odfdo
odfdo
Python library for OpenDocument format (ODF)

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format
standard.
Project: https://github.com/jdum/odfdo
Author: jerome.dumonteil@gmail.com
License: Apache License, Version 2.0
odfdo is a derivative work of the former lpod-python project.
Installation
Installation from Pypi (recommended):
pip install odfdo
Installation from sources (requiring setuptools):
pip install .
After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):
pytest
The tests should run for a few seconds or minutes and issue no error.
Usage
from odfdo import Document, Paragraph
doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")
tl;dr
'Intended Audience :: Developers'
Documentation
There is no detailed documentation or tutorial, but:
- the
recipesfolder contains more than 50 working sample scripts, - the
docfolder contains an auto generated documentation.
When installing odfdo, a few scripts are installed:
odfdo-diff: show a diff between two .odt document.odfdo-folder: convert standard ODF file to folder and files, and reverse.odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.odfdo-styles: command line interface tool to manipulate styles of ODF files.odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.odfdo-headers: print the headers of an ODF file.
About styles: the best way to apply style is by merging styles from a template
document into your generated document (See odfdo-styles script).
Styles are a complex matter in ODF, so trying to generate styles programmatically
is not recommended.
Limitations
odfdo is intended to facilitate the generation of ODF documents,
nevertheless a basic knowledge of the ODF format is necessary.
ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.
The best (only ?) way to apply style is by merging styles from a template document into your generated document.
Related project
I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator
Changes from former lpod library
lpod-python was written in 2009-2010 as a Python 2.x library,
see: https://github.com/lpod/lpod-python
odfdo is an adaptation of this former project. odfdo main changes from lpod:
odfdorequires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.- API change: more pythonic.
- include recipes.
- use Apache 2.0 license.
1# Copyright 2018-2024 Jérôme Dumonteil 2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16# 17# Authors (odfdo project): jerome.dumonteil@gmail.com 18# The odfdo project is a derivative work of the lpod-python project: 19# https://github.com/lpod/lpod-python 20# Authors: David Versmisse <david.versmisse@itaapy.com> 21# Hervé Cauwelier <herve@itaapy.com> 22# Romain Gauthier <romain@itaapy.com> 23""" 24.. include:: ../README.md 25""" 26 27__all__ = [ 28 "AnimPar", 29 "AnimSeq", 30 "AnimTransFilter", 31 "Annotation", 32 "AnnotationEnd", 33 "BackgroundImage", 34 "Bookmark", 35 "BookmarkEnd", 36 "BookmarkStart", 37 "Cell", 38 "ChangeInfo", 39 "Column", 40 "ConnectorShape", 41 "Container", 42 "Content", 43 "Content", 44 "Document", 45 "DrawFillImage", 46 "DrawGroup", 47 "DrawImage", 48 "DrawPage", 49 "Element", 50 "ElementTyped", 51 "EllipseShape", 52 "FIRST_CHILD", 53 "Frame", 54 "Header", 55 "HeaderRows", 56 "IndexTitle", 57 "IndexTitleTemplate", 58 "LAST_CHILD", 59 "LineBreak", 60 "LineShape", 61 "Link", 62 "List", 63 "ListItem", 64 "Manifest", 65 "Meta", 66 "NEXT_SIBLING", 67 "NamedRange", 68 "Note", 69 "PREV_SIBLING", 70 "PageBreak", 71 "Paragraph", 72 "RectangleShape", 73 "Reference", 74 "ReferenceMark", 75 "ReferenceMarkEnd", 76 "ReferenceMarkStart", 77 "Row", 78 "RowGroup", 79 "Section", 80 "Spacer", 81 "Span", 82 "Style", 83 "Styles", 84 "TOC", 85 "Tab", 86 "TabStopStyle", 87 "Table", 88 "Text", 89 "TextChange", 90 "TextChangeEnd", 91 "TextChangeStart", 92 "TextChangedRegion", 93 "TextDeletion", 94 "TextFormatChange", 95 "TextInsertion", 96 "TocEntryTemplate", 97 "TrackedChanges", 98 "UserDefined", 99 "UserFieldDecl", 100 "UserFieldDecls", 101 "UserFieldGet", 102 "UserFieldInput", 103 "VarChapter", 104 "VarCreationDate", 105 "VarCreationTime", 106 "VarDate", 107 "VarDecl", 108 "VarDecls", 109 "VarDescription", 110 "VarFileName", 111 "VarGet", 112 "VarInitialCreator", 113 "VarKeywords", 114 "VarPageCount", 115 "VarPageNumber", 116 "VarSet", 117 "VarSubject", 118 "VarTime", 119 "VarTitle", 120 "XmlPart", 121 "__version__", 122 "create_table_cell_style", 123 "default_boolean_style", 124 "default_currency_style", 125 "default_date_style", 126 "default_frame_position_style", 127 "default_number_style", 128 "default_percentage_style", 129 "default_time_style", 130 "default_toc_level_style", 131 "hex2rgb", 132 "hexa_color", 133 "make_table_cell_border_string", 134 "rgb2hex", 135] 136 137 138from .bookmark import Bookmark, BookmarkEnd, BookmarkStart 139from .cell import Cell 140from .container import Container 141from .content import Content 142from .document import Document 143from .draw_page import DrawPage 144from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text 145from .element_typed import ElementTyped 146from .frame import Frame, default_frame_position_style 147from .header import Header 148from .header_rows import HeaderRows 149from .image import DrawFillImage, DrawImage 150from .link import Link 151from .list import List, ListItem 152from .manifest import Manifest 153from .meta import Meta 154from .note import Annotation, AnnotationEnd, Note 155from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab 156from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart 157from .section import Section 158from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape 159from .smil import AnimPar, AnimSeq, AnimTransFilter 160from .style import ( 161 BackgroundImage, 162 Style, 163 create_table_cell_style, 164 default_boolean_style, 165 default_currency_style, 166 default_date_style, 167 default_number_style, 168 default_percentage_style, 169 default_time_style, 170 make_table_cell_border_string, 171) 172from .styles import Styles 173from .table import Column, NamedRange, Row, RowGroup, Table 174from .toc import ( 175 TOC, 176 IndexTitle, 177 IndexTitleTemplate, 178 TabStopStyle, 179 TocEntryTemplate, 180 default_toc_level_style, 181) 182from .tracked_changes import ( 183 ChangeInfo, 184 TextChange, 185 TextChangedRegion, 186 TextChangeEnd, 187 TextChangeStart, 188 TextDeletion, 189 TextFormatChange, 190 TextInsertion, 191 TrackedChanges, 192) 193from .utils import hex2rgb, hexa_color, rgb2hex 194from .variable import ( 195 UserDefined, 196 UserFieldDecl, 197 UserFieldDecls, 198 UserFieldGet, 199 UserFieldInput, 200 VarChapter, 201 VarCreationDate, 202 VarCreationTime, 203 VarDate, 204 VarDecl, 205 VarDecls, 206 VarDescription, 207 VarFileName, 208 VarGet, 209 VarInitialCreator, 210 VarKeywords, 211 VarPageCount, 212 VarPageNumber, 213 VarSet, 214 VarSubject, 215 VarTime, 216 VarTitle, 217) 218from .version import __version__ 219from .xmlpart import XmlPart
32class AnimPar(Element): 33 """A container for SMIL Presentation Animations. 34 35 Arguments: 36 37 presentation_node_type -- default, on-click, with-previous, 38 after-previous, timing-root, main-sequence 39 and interactive-sequence 40 41 smil_begin -- indefinite, 10s, [id].click, [id].begin 42 """ 43 44 _tag = "anim:par" 45 _properties = ( 46 PropDef("presentation_node_type", "presentation:node-type"), 47 PropDef("smil_begin", "smil:begin"), 48 ) 49 50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
A container for SMIL Presentation Animations.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
smil_begin -- indefinite, 10s, [id].click, [id].begin
50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
67class AnimSeq(Element): 68 """TA container for SMIL Presentation Animations. Animations 69 inside this block are executed after the slide has executed its initial 70 transition. 71 72 Arguments: 73 74 presentation_node_type -- default, on-click, with-previous, 75 after-previous, timing-root, main-sequence 76 and interactive-sequence 77 """ 78 79 _tag = "anim:seq" 80 _properties = (PropDef("presentation_node_type", "presentation:node-type"),) 81 82 def __init__( 83 self, 84 presentation_node_type: str | None = None, 85 **kwargs: Any, 86 ) -> None: 87 super().__init__(**kwargs) 88 if self._do_init and presentation_node_type: 89 self.presentation_node_type = presentation_node_type
TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
95class AnimTransFilter(Element): 96 """ 97 Class to make a beautiful transition between two frames. 98 99 Arguments: 100 smil_dur -- XXX complete me 101 102 smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ 103 smil-transitions.html#TransitionEffects-Appendix 104 to get a list of all types/subtypes 105 106 smil_direction -- forward, reverse 107 108 smil_fadeColor -- forward, reverse 109 110 smil_mode -- in, out 111 """ 112 113 _tag = "anim:transitionFilter" 114 _properties = ( 115 PropDef("smil_dur", "smil:dur"), 116 PropDef("smil_type", "smil:type"), 117 PropDef("smil_subtype", "smil:subtype"), 118 PropDef("smil_direction", "smil:direction"), 119 PropDef("smil_fadeColor", "smil:fadeColor"), 120 PropDef("smil_mode", "smil:mode"), 121 ) 122 123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
Class to make a beautiful transition between two frames.
Arguments: smil_dur -- XXX complete me
smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes
smil_direction -- forward, reverse
smil_fadeColor -- forward, reverse
smil_mode -- in, out
123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
147class Annotation(Element): 148 """Annotation element credited to the given creator with the 149 given text, optionally dated (current date by default). 150 If name not provided and some parent is provided, the name is 151 autogenerated. 152 153 Arguments: 154 155 text -- str or odf_element 156 157 creator -- str 158 159 date -- datetime 160 161 name -- str 162 163 parent -- Element 164 """ 165 166 _tag = "office:annotation" 167 _properties = ( 168 PropDef("name", "office:name"), 169 PropDef("note_id", "text:id"), 170 ) 171 172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name 195 196 @property 197 def note_body(self) -> str: 198 return self.text_content 199 200 @note_body.setter 201 def note_body(self, text_or_element: Element | str | None) -> None: 202 if text_or_element is None: 203 self.text_content = "" 204 elif isinstance(text_or_element, str): 205 self.text_content = text_or_element 206 elif isinstance(text_or_element, Element): 207 self.clear() 208 self.append(text_or_element) 209 else: 210 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 211 212 @property 213 def start(self) -> Element: 214 """Return self.""" 215 return self 216 217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name) 228 229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 ) 265 266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete() 285 286 def check_validity(self) -> None: 287 if not self.note_body: 288 raise ValueError("Annotation must have a body") 289 if not self.dc_creator: 290 raise ValueError("Annotation must have a creator") 291 if not self.dc_date: 292 self.dc_date = datetime.now()
Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.
Arguments:
text -- str or odf_element
creator -- str
date -- datetime
name -- str
parent -- Element
172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name
217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name)
Return the corresponding annotation-end tag or None.
229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 )
Returns the annotated content from an annotation.
If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text or None
266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For Annotation : delete the annotation-end tag if exists.
Arguments:
child -- Element or None
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
298class AnnotationEnd(Element): 299 """AnnotationEnd: the "office:annotation-end" element may be used to 300 define the end of a text range of document content that spans element 301 boundaries. In that case, an "office:annotation" element shall precede 302 the "office:annotation-end" element. Both elements shall have the same 303 value for their office:name attribute. The "office:annotation-end" element 304 shall be preceded by an "office:annotation" element that has the same 305 value for its office:name attribute as the "office:annotation-end" 306 element. An "office:annotation-end" element without a preceding 307 "office:annotation" element that has the same name assigned is ignored. 308 """ 309 310 _tag = "office:annotation-end" 311 _properties = (PropDef("name", "office:name"),) 312 313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name 337 338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name) 349 350 @property 351 def end(self) -> Element: 352 """Return self.""" 353 return self
AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.
313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name
Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.
Arguments:
annotation -- odf_annotation element
name -- str
338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name)
Return the corresponding annotation starting tag or None.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
999class BackgroundImage(Style, DrawImage): 1000 _tag = "style:background-image" 1001 _properties: tuple[PropDef, ...] = ( 1002 PropDef("name", "style:name"), 1003 PropDef("display_name", "style:display-name"), 1004 PropDef("svg_font_family", "svg:font-family"), 1005 PropDef("font_family_generic", "style:font-family-generic"), 1006 PropDef("font_pitch", "style:font-pitch"), 1007 PropDef("position", "style:position", "background-image"), 1008 PropDef("repeat", "style:repeat", "background-image"), 1009 PropDef("opacity", "draw:opacity", "background-image"), 1010 PropDef("filter", "style:filter-name", "background-image"), 1011 PropDef("text_style", "text:style-name"), 1012 ) 1013 1014 def __init__( 1015 self, 1016 name: str | None = None, 1017 display_name: str | None = None, 1018 position: str | None = None, 1019 repeat: str | None = None, 1020 opacity: str | None = None, 1021 filter: str | None = None, # noqa: A002 1022 # Every other property 1023 **kwargs: Any, 1024 ): 1025 kwargs["family"] = "background-image" 1026 super().__init__(**kwargs) 1027 if self._do_init: 1028 kwargs.pop("tag", None) 1029 kwargs.pop("tag_or_elem", None) 1030 self.family = "background-image" 1031 if name: 1032 self.name = name 1033 if display_name: 1034 self.display_name = display_name 1035 if position: 1036 self.position = position 1037 if repeat: 1038 self.position = repeat 1039 if opacity: 1040 self.position = opacity 1041 if filter: 1042 self.position = filter 1043 # Every other properties 1044 for prop in BackgroundImage._properties: 1045 if prop.name in kwargs: 1046 self.set_style_attribute(prop.attr, kwargs[prop.name])
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
1014 def __init__( 1015 self, 1016 name: str | None = None, 1017 display_name: str | None = None, 1018 position: str | None = None, 1019 repeat: str | None = None, 1020 opacity: str | None = None, 1021 filter: str | None = None, # noqa: A002 1022 # Every other property 1023 **kwargs: Any, 1024 ): 1025 kwargs["family"] = "background-image" 1026 super().__init__(**kwargs) 1027 if self._do_init: 1028 kwargs.pop("tag", None) 1029 kwargs.pop("tag_or_elem", None) 1030 self.family = "background-image" 1031 if name: 1032 self.name = name 1033 if display_name: 1034 self.display_name = display_name 1035 if position: 1036 self.position = position 1037 if repeat: 1038 self.position = repeat 1039 if opacity: 1040 self.position = opacity 1041 if filter: 1042 self.position = filter 1043 # Every other properties 1044 for prop in BackgroundImage._properties: 1045 if prop.name in kwargs: 1046 self.set_style_attribute(prop.attr, kwargs[prop.name])
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Style
- family
- get_properties
- set_properties
- del_properties
- set_background
- get_level_style
- set_level_style
- get_header_style
- set_header_style
- get_page_header
- set_page_header
- set_font
- page_layout
- next_style
- parent_style
- master_page
- style_type
- leader_style
- leader_text
- style_position
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Bookmark(Element): 32 """ 33 Bookmark class for ODF "text:bookmark" 34 35 Arguments: 36 37 name -- str 38 """ 39 40 _tag = "text:bookmark" 41 _properties = (PropDef("name", "text:name"),) 42 43 def __init__(self, name: str = "", **kwargs: Any) -> None: 44 super().__init__(**kwargs) 45 if self._do_init: 46 self.name = name
Bookmark class for ODF "text:bookmark"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
73class BookmarkEnd(Element): 74 """ 75 BookmarkEnd class for ODF "text:bookmark-end" 76 77 Arguments: 78 79 name -- str 80 """ 81 82 _tag = "text:bookmark-end" 83 _properties = (PropDef("name", "text:name"),) 84 85 def __init__(self, name: str = "", **kwargs: Any) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.name = name
BookmarkEnd class for ODF "text:bookmark-end"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class BookmarkStart(Element): 53 """ 54 BookmarkStart class for ODF "text:bookmark-start" 55 56 Arguments: 57 58 name -- str 59 """ 60 61 _tag = "text:bookmark-start" 62 _properties = (PropDef("name", "text:name"),) 63 64 def __init__(self, name: str = "", **kwargs: Any) -> None: 65 super().__init__(**kwargs) 66 if self._do_init: 67 self.name = name
BookmarkStart class for ODF "text:bookmark-start"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
44class Cell(ElementTyped): 45 """ "table:table-cell" table cell element.""" 46 47 _tag = "table:table-cell" 48 _caching = True 49 50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 99 @property 100 def clone(self) -> Cell: 101 clone = Element.clone.fget(self) # type: ignore 102 clone.y = self.y 103 clone.x = self.x 104 if hasattr(self, "_tmap"): 105 if hasattr(self, "_rmap"): 106 clone._rmap = self._rmap[:] 107 clone._tmap = self._tmap[:] 108 clone._cmap = self._cmap[:] 109 return clone 110 111 @property 112 def value( 113 self, 114 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 115 """Set / get the value of the cell. The type is read from the 116 'office:value-type' attribute of the cell. When setting the value, 117 the type of the value will determine the new value_type of the cell. 118 119 Warning: use this method for boolean, float or string only. 120 """ 121 value_type = self.get_attribute_string("office:value-type") 122 if value_type == "boolean": 123 return self.get_attribute("office:boolean-value") 124 if value_type in {"float", "percentage", "currency"}: 125 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 126 # Return 3 instead of 3.0 if possible 127 if int(value_decimal) == value_decimal: 128 return int(value_decimal) 129 return value_decimal 130 if value_type == "date": 131 value_str = str(self.get_attribute_string("office:date-value")) 132 if "T" in value_str: 133 return DateTime.decode(value_str) 134 return Date.decode(value_str) 135 if value_type == "time": 136 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 137 if value_type == "string": 138 value = self.get_attribute_string("office:string-value") 139 if value is not None: 140 return value 141 value_list = [] 142 for para in self.get_elements("text:p"): 143 value_list.append(para.text_recursive) 144 return "\n".join(value_list) 145 return None 146 147 @value.setter 148 def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None: 149 self.clear() 150 if value is None: 151 return 152 if isinstance(value, (str, bytes)): 153 if isinstance(value, bytes): 154 value = bytes_to_str(value) 155 self.set_attribute("office:value-type", "string") 156 self.set_attribute("office:string-value", value) 157 self.text = value 158 return 159 if value is True or value is False: 160 self.set_attribute("office:value-type", "boolean") 161 value_bool = Boolean.encode(value) 162 self.set_attribute("office:boolean-value", value_bool) 163 self.text = value_bool 164 return 165 if isinstance(value, (int, Float, Decimal)): 166 self.set_attribute("office:value-type", "float") 167 value_str = str(value) 168 self.set_attribute("office:value", value_str) 169 self.text = value_str 170 return 171 raise TypeError(f"Unknown value type, try with set_value() : {value!r}") 172 173 @property 174 def float(self) -> Float: 175 """Set / get the value of the cell as a float (or 0.0).""" 176 for tag in ("office:value", "office:string-value", "office:boolean-value"): 177 read_attr = self.get_attribute(tag) 178 if isinstance(read_attr, str): 179 with contextlib.suppress(ValueError, TypeError): 180 return Float(read_attr) 181 return 0.0 182 183 @float.setter 184 def float(self, value: str | Float | int | Decimal) -> None: 185 try: 186 value_float = Float(value) 187 except (ValueError, TypeError): 188 value_float = 0.0 189 value_str = str(value_float) 190 self.clear() 191 self.set_attribute("office:value", value_str) 192 self.set_attribute("office:value-type", "float") 193 self.text = value_str 194 195 @property 196 def string(self) -> str: 197 """Set / get the value of the cell as a string (or '').""" 198 value = self.get_attribute_string("office:string-value") 199 if isinstance(value, str): 200 return value 201 return "" 202 203 @string.setter 204 def string( 205 self, 206 value: str | bytes | int | Float | Decimal | bool | None, # type: ignore 207 ) -> None: 208 self.clear() 209 if value is None: 210 value_str = "" 211 else: 212 value_str = str(value) 213 self.set_attribute("office:value-type", "string") 214 self.set_attribute("office:string-value", value_str) 215 self.text = value_str 216 217 def set_value( 218 self, 219 value: ( 220 str # type: ignore 221 | bytes 222 | Float 223 | int 224 | Decimal 225 | bool 226 | datetime 227 | date 228 | timedelta 229 | None 230 ), 231 text: str | None = None, 232 cell_type: str | None = None, 233 currency: str | None = None, 234 formula: str | None = None, 235 ) -> None: 236 """Set the cell state from the Python value type. 237 238 Text is how the cell is displayed. Cell type is guessed, 239 unless provided. 240 241 For monetary values, provide the name of the currency. 242 243 Arguments: 244 245 value -- Python type 246 247 text -- str 248 249 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 250 'currency' or 'percentage' 251 252 currency -- str 253 """ 254 self.clear() 255 text = self.set_value_and_type( 256 value=value, 257 text=text, 258 value_type=cell_type, 259 currency=currency, 260 ) 261 if text is not None: 262 self.text_content = text 263 if formula is not None: 264 self.formula = formula 265 266 @property 267 def type(self) -> str | None: 268 """Get / set the type of the cell: boolean, float, date, string 269 or time. 270 271 Return: str | None 272 """ 273 return self.get_attribute_string("office:value-type") 274 275 @type.setter 276 def type(self, cell_type: str) -> None: 277 self.set_attribute("office:value-type", cell_type) 278 279 @property 280 def currency(self) -> str | None: 281 """Get / set the currency used for monetary values. 282 283 Return: str | None 284 """ 285 return self.get_attribute_string("office:currency") 286 287 @currency.setter 288 def currency(self, currency: str) -> None: 289 self.set_attribute("office:currency", currency) 290 291 def _set_repeated(self, repeated: int | None) -> None: 292 """Internal only. Set the numnber of times the cell is repeated, or 293 None to delete. Without changing cache. 294 """ 295 if repeated is None or repeated < 2: 296 with contextlib.suppress(KeyError): 297 self.del_attribute("table:number-columns-repeated") 298 return 299 self.set_attribute("table:number-columns-repeated", str(repeated)) 300 301 @property 302 def repeated(self) -> int | None: 303 """Get / set the number of times the cell is repeated. 304 305 Always None when using the table API. 306 307 Return: int or None 308 """ 309 repeated = self.get_attribute("table:number-columns-repeated") 310 if repeated is None: 311 return None 312 return int(repeated) 313 314 @repeated.setter 315 def repeated(self, repeated: int | None) -> None: 316 self._set_repeated(repeated) 317 # update cache 318 child: Element = self 319 while True: 320 # look for Row, parent may be group of rows 321 upper = child.parent 322 if not upper: 323 # lonely cell 324 return 325 # parent may be group of rows, not table 326 if isinstance(upper, Element) and upper._tag == "table:table-row": 327 break 328 child = upper 329 # fixme : need to optimize this 330 if isinstance(upper, Element) and upper._tag == "table:table-row": 331 upper._compute_row_cache() 332 333 @property 334 def style(self) -> str | None: 335 """Get / set the style of the cell itself. 336 337 Return: str | None 338 """ 339 return self.get_attribute_string("table:style-name") 340 341 @style.setter 342 def style(self, style: str | Element) -> None: 343 self.set_style_attribute("table:style-name", style) 344 345 @property 346 def formula(self) -> str | None: 347 """Get / set the formula of the cell, or None if undefined. 348 349 The formula is not interpreted in any way. 350 351 Return: str | None 352 """ 353 return self.get_attribute_string("table:formula") 354 355 @formula.setter 356 def formula(self, formula: str | None) -> None: 357 self.set_attribute("table:formula", formula) 358 359 def is_empty(self, aggressive: bool = False) -> bool: 360 if self.value is not None or self.children: 361 return False 362 if not aggressive and self.style is not None: 363 return False 364 return True 365 366 def _is_spanned(self) -> bool: 367 if self.tag == "table:covered-table-cell": 368 return True 369 if self.get_attribute("table:number-columns-spanned") is not None: 370 return True 371 if self.get_attribute("table:number-rows-spanned") is not None: 372 return True 373 return False
"table:table-cell" table cell element.
50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style
Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.
Arguments:
value -- bool, int, float, Decimal, date, datetime, str,
timedelta
text -- str
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
repeated -- int
style -- str
99 @property 100 def clone(self) -> Cell: 101 clone = Element.clone.fget(self) # type: ignore 102 clone.y = self.y 103 clone.x = self.x 104 if hasattr(self, "_tmap"): 105 if hasattr(self, "_rmap"): 106 clone._rmap = self._rmap[:] 107 clone._tmap = self._tmap[:] 108 clone._cmap = self._cmap[:] 109 return clone
111 @property 112 def value( 113 self, 114 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 115 """Set / get the value of the cell. The type is read from the 116 'office:value-type' attribute of the cell. When setting the value, 117 the type of the value will determine the new value_type of the cell. 118 119 Warning: use this method for boolean, float or string only. 120 """ 121 value_type = self.get_attribute_string("office:value-type") 122 if value_type == "boolean": 123 return self.get_attribute("office:boolean-value") 124 if value_type in {"float", "percentage", "currency"}: 125 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 126 # Return 3 instead of 3.0 if possible 127 if int(value_decimal) == value_decimal: 128 return int(value_decimal) 129 return value_decimal 130 if value_type == "date": 131 value_str = str(self.get_attribute_string("office:date-value")) 132 if "T" in value_str: 133 return DateTime.decode(value_str) 134 return Date.decode(value_str) 135 if value_type == "time": 136 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 137 if value_type == "string": 138 value = self.get_attribute_string("office:string-value") 139 if value is not None: 140 return value 141 value_list = [] 142 for para in self.get_elements("text:p"): 143 value_list.append(para.text_recursive) 144 return "\n".join(value_list) 145 return None
Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.
Warning: use this method for boolean, float or string only.
173 @property 174 def float(self) -> Float: 175 """Set / get the value of the cell as a float (or 0.0).""" 176 for tag in ("office:value", "office:string-value", "office:boolean-value"): 177 read_attr = self.get_attribute(tag) 178 if isinstance(read_attr, str): 179 with contextlib.suppress(ValueError, TypeError): 180 return Float(read_attr) 181 return 0.0
Set / get the value of the cell as a float (or 0.0).
195 @property 196 def string(self) -> str: 197 """Set / get the value of the cell as a string (or '').""" 198 value = self.get_attribute_string("office:string-value") 199 if isinstance(value, str): 200 return value 201 return ""
Set / get the value of the cell as a string (or '').
217 def set_value( 218 self, 219 value: ( 220 str # type: ignore 221 | bytes 222 | Float 223 | int 224 | Decimal 225 | bool 226 | datetime 227 | date 228 | timedelta 229 | None 230 ), 231 text: str | None = None, 232 cell_type: str | None = None, 233 currency: str | None = None, 234 formula: str | None = None, 235 ) -> None: 236 """Set the cell state from the Python value type. 237 238 Text is how the cell is displayed. Cell type is guessed, 239 unless provided. 240 241 For monetary values, provide the name of the currency. 242 243 Arguments: 244 245 value -- Python type 246 247 text -- str 248 249 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 250 'currency' or 'percentage' 251 252 currency -- str 253 """ 254 self.clear() 255 text = self.set_value_and_type( 256 value=value, 257 text=text, 258 value_type=cell_type, 259 currency=currency, 260 ) 261 if text is not None: 262 self.text_content = text 263 if formula is not None: 264 self.formula = formula
Set the cell state from the Python value type.
Text is how the cell is displayed. Cell type is guessed, unless provided.
For monetary values, provide the name of the currency.
Arguments:
value -- Python type
text -- str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- str
266 @property 267 def type(self) -> str | None: 268 """Get / set the type of the cell: boolean, float, date, string 269 or time. 270 271 Return: str | None 272 """ 273 return self.get_attribute_string("office:value-type")
Get / set the type of the cell: boolean, float, date, string or time.
Return: str | None
279 @property 280 def currency(self) -> str | None: 281 """Get / set the currency used for monetary values. 282 283 Return: str | None 284 """ 285 return self.get_attribute_string("office:currency")
Get / set the currency used for monetary values.
Return: str | None
301 @property 302 def repeated(self) -> int | None: 303 """Get / set the number of times the cell is repeated. 304 305 Always None when using the table API. 306 307 Return: int or None 308 """ 309 repeated = self.get_attribute("table:number-columns-repeated") 310 if repeated is None: 311 return None 312 return int(repeated)
Get / set the number of times the cell is repeated.
Always None when using the table API.
Return: int or None
333 @property 334 def style(self) -> str | None: 335 """Get / set the style of the cell itself. 336 337 Return: str | None 338 """ 339 return self.get_attribute_string("table:style-name")
Get / set the style of the cell itself.
Return: str | None
345 @property 346 def formula(self) -> str | None: 347 """Get / set the formula of the cell, or None if undefined. 348 349 The formula is not interpreted in any way. 350 351 Return: str | None 352 """ 353 return self.get_attribute_string("table:formula")
Get / set the formula of the cell, or None if undefined.
The formula is not interpreted in any way.
Return: str | None
359 def is_empty(self, aggressive: bool = False) -> bool: 360 if self.value is not None or self.children: 361 return False 362 if not aggressive and self.style is not None: 363 return False 364 return True
Check if the element is empty : no text, no children, no tail.
Return: Boolean
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class ChangeInfo(Element): 38 """The "office:change-info" element represents who made a change and when. 39 It may also contain a comment (one or more Paragrah "text:p" elements) 40 on the change. 41 42 The comments available in the ChangeInfo are available through: 43 - get_paragraphs and get_paragraph methods for actual Paragraph. 44 - get_comments for a plain text version 45 46 Arguments: 47 48 creator -- str (or None) 49 50 date -- datetime (or None) 51 """ 52 53 _tag = "office:change-info" 54 55 def __init__( 56 self, 57 creator: str | None = None, 58 date: datetime | None = None, 59 **kwargs: Any, 60 ) -> None: 61 super().__init__(**kwargs) 62 if self._do_init: 63 self.set_dc_creator(creator) 64 self.set_dc_date(date) 65 66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator 80 81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate 96 97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text 115 116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.
The comments available in the ChangeInfo are available through:
- get_paragraphs and get_paragraph methods for actual Paragraph.
- get_comments for a plain text version
Arguments:
creator -- str (or None)
date -- datetime (or None)
66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator
Set the creator of the change. Default for creator is 'Unknown'.
Arguments:
creator -- str (or None)
81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate
Set the date of the change. If date is None, use current time.
Arguments:
date -- datetime (or None)
97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text
Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.
Arguments:
joined -- boolean (default is True)
Return: str or list of str.
116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.
Arguments:
text -- str
replace -- boolean
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
164class Column(Element): 165 """ODF table column "table:table-column" """ 166 167 _tag = "table:table-column" 168 _caching = True 169 170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style 202 203 @property 204 def clone(self) -> Column: 205 clone = Element.clone.fget(self) # type: ignore 206 clone.x = self.x 207 if hasattr(self, "_tmap"): 208 if hasattr(self, "_rmap"): 209 clone._rmap = self._rmap[:] 210 clone._tmap = self._tmap[:] 211 clone._cmap = self._cmap[:] 212 return clone 213 214 def get_default_cell_style(self) -> str | None: 215 return self.get_attribute_string("table:default-cell-style-name") 216 217 def set_default_cell_style(self, style: Element | str) -> None: 218 self.set_style_attribute("table:default-cell-style-name", style) 219 220 def _set_repeated(self, repeated: int | None) -> None: 221 """Internal only. Set the number of times the column is repeated, or 222 None to delete it. Without changing cache. 223 224 Arguments: 225 226 repeated -- int or None 227 """ 228 if repeated is None or repeated < 2: 229 with contextlib.suppress(KeyError): 230 self.del_attribute("table:number-columns-repeated") 231 return 232 self.set_attribute("table:number-columns-repeated", str(repeated)) 233 234 @property 235 def repeated(self) -> int | None: 236 """Get /set the number of times the column is repeated. 237 238 Always None when using the table API. 239 240 Return: int or None 241 """ 242 repeated = self.get_attribute("table:number-columns-repeated") 243 if repeated is None: 244 return None 245 return int(repeated) 246 247 @repeated.setter 248 def repeated(self, repeated: int | None) -> None: 249 self._set_repeated(repeated) 250 # update cache 251 current: Element = self 252 while True: 253 # look for Table, parent may be group of rows 254 upper = current.parent 255 if not upper: 256 # lonely column 257 return 258 # parent may be group of rows, not table 259 if isinstance(upper, Table): 260 break 261 current = upper 262 # fixme : need to optimize this 263 if isinstance(upper, Table): 264 upper._compute_table_cache() 265 if hasattr(self, "_cmap"): 266 del self._cmap[:] 267 self._cmap.extend(upper._cmap) 268 else: 269 self._cmap = upper._cmap 270 271 @property 272 def style(self) -> str | None: 273 """Get /set the style of the column itself. 274 275 Return: str 276 """ 277 return self.get_attribute_string("table:style-name") 278 279 @style.setter 280 def style(self, style: str | Element) -> None: 281 self.set_style_attribute("table:style-name", style)
ODF table column "table:table-column"
170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style
Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.
Columns don't contain cells, just style information.
You don't generally have to create columns by hand, use the Table API.
Arguments:
default_cell_style -- str
repeated -- int
style -- str
234 @property 235 def repeated(self) -> int | None: 236 """Get /set the number of times the column is repeated. 237 238 Always None when using the table API. 239 240 Return: int or None 241 """ 242 repeated = self.get_attribute("table:number-columns-repeated") 243 if repeated is None: 244 return None 245 return int(repeated)
Get /set the number of times the column is repeated.
Always None when using the table API.
Return: int or None
271 @property 272 def style(self) -> str | None: 273 """Get /set the style of the column itself. 274 275 Return: str 276 """ 277 return self.get_attribute_string("table:style-name")
Get /set the style of the column itself.
Return: str
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
242class ConnectorShape(ShapeBase): 243 """Create a Connector shape. 244 245 Arguments: 246 247 style -- str 248 249 text_style -- str 250 251 draw_id -- str 252 253 layer -- str 254 255 connected_shapes -- (shape, shape) 256 257 glue_points -- (point, point) 258 259 p1 -- (str, str) 260 261 p2 -- (str, str) 262 """ 263 264 _tag = "draw:connector" 265 _properties: tuple[PropDef, ...] = ( 266 PropDef("start_shape", "draw:start-shape"), 267 PropDef("end_shape", "draw:end-shape"), 268 PropDef("start_glue_point", "draw:start-glue-point"), 269 PropDef("end_glue_point", "draw:end-glue-point"), 270 PropDef("x1", "svg:x1"), 271 PropDef("y1", "svg:y1"), 272 PropDef("x2", "svg:x2"), 273 PropDef("y2", "svg:y2"), 274 ) 275 276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
Create a Connector shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
connected_shapes -- (shape, shape)
glue_points -- (point, point)
p1 -- (str, str)
p2 -- (str, str)
276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
55class Container: 56 """Representation of the ODF file.""" 57 58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path) 66 67 def __repr__(self) -> str: 68 return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>" 69 70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file) 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile( 78 path_or_file 79 ): 80 self.__packaging = "zip" 81 return self._read_zip() 82 if self.path: 83 is_folder = False 84 with contextlib.suppress(OSError): 85 is_folder = self.path.is_dir() 86 if is_folder: 87 self.__packaging = "folder" 88 return self._read_folder() 89 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.") 90 91 def _read_zip(self) -> None: 92 if isinstance(self.__path_like, io.BytesIO): 93 self.__path_like.seek(0) 94 with ZipFile(self.__path_like) as zf: # type: ignore 95 mimetype = bytes_to_str(zf.read("mimetype")) 96 if mimetype not in ODF_MIMETYPES: 97 raise ValueError(f"Document of unknown type {mimetype}") 98 self.__parts["mimetype"] = str_to_bytes(mimetype) 99 if self.path is None: 100 if isinstance(self.__path_like, io.BytesIO): 101 self.__path_like.seek(0) 102 # read the full file at once and forget file 103 with ZipFile(self.__path_like) as zf: # type: ignore 104 for name in zf.namelist(): 105 upath = normalize_path(name) 106 self.__parts[upath] = zf.read(name) 107 self.__path_like = None 108 109 def _read_folder(self) -> None: 110 try: 111 mimetype, timestamp = self._get_folder_part("mimetype") 112 except OSError: 113 printwarn("Corrupted or not an OpenDocument folder (missing mimetype)") 114 mimetype = b"" 115 timestamp = int(time.time()) 116 if bytes_to_str(mimetype) not in ODF_MIMETYPES: 117 message = f"Document of unknown type {mimetype!r}, try with ODF Text." 118 printwarn(message) 119 self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"]) 120 self.__parts_ts["mimetype"] = timestamp 121 122 def _parse_folder(self, folder: str) -> list[str]: 123 parts = [] 124 if self.path is None: 125 raise ValueError("Document path is not defined") 126 root = self.path / folder 127 for path in root.iterdir(): 128 if path.name.startswith("."): # no hidden files 129 continue 130 relative_path = path.relative_to(self.path) 131 if path.is_file(): 132 parts.append(relative_path.as_posix()) 133 if path.is_dir(): 134 sub_parts = self._parse_folder(str(relative_path)) 135 if sub_parts: 136 parts.extend(sub_parts) 137 else: 138 # store leaf directories 139 parts.append(relative_path.as_posix() + "/") 140 return parts 141 142 def _get_folder_parts(self) -> list[str]: 143 """Get the list of members in the ODF folder.""" 144 return self._parse_folder("") 145 146 def _get_folder_part(self, name: str) -> tuple[bytes, int]: 147 """Get bytes of a part from the ODF folder, with timestamp.""" 148 if self.path is None: 149 raise ValueError("Document path is not defined") 150 path = self.path / name 151 timestamp = int(path.stat().st_mtime) 152 if path.is_dir(): 153 return (b"", timestamp) 154 return (path.read_bytes(), timestamp) 155 156 def _get_folder_part_timestamp(self, name: str) -> int: 157 if self.path is None: 158 raise ValueError("Document path is not defined") 159 path = self.path / name 160 try: 161 timestamp = int(path.stat().st_mtime) 162 except OSError: 163 timestamp = -1 164 return timestamp 165 166 def _get_zip_part(self, name: str) -> bytes | None: 167 """Get bytes of a part from the Zip ODF file. No cache.""" 168 if self.path is None: 169 raise ValueError("Document path is not defined") 170 try: 171 with ZipFile(self.path) as zf: 172 upath = normalize_path(name) 173 self.__parts[upath] = zf.read(name) 174 return self.__parts[upath] 175 except BadZipfile: 176 return None 177 178 def _get_all_zip_part(self) -> None: 179 """Read all parts. No cache.""" 180 if self.path is None: 181 raise ValueError("Document path is not defined") 182 try: 183 with ZipFile(self.path) as zf: 184 for name in zf.namelist(): 185 upath = normalize_path(name) 186 self.__parts[upath] = zf.read(name) 187 except BadZipfile: 188 pass 189 190 def _save_zip(self, target: str | Path | io.BytesIO) -> None: 191 """Save a Zip ODF from the available parts.""" 192 parts = self.__parts 193 with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip: 194 # Parts to save, except manifest at the end 195 part_names = list(parts.keys()) 196 try: 197 part_names.remove(ODF_MANIFEST) 198 except ValueError: 199 printwarn(f"Missing '{ODF_MANIFEST}'") 200 # "Pretty-save" parts in some order 201 # mimetype requires to be first and uncompressed 202 mimetype = parts.get("mimetype") 203 if mimetype is None: 204 raise ValueError("Mimetype is not defined") 205 try: 206 filezip.writestr("mimetype", mimetype, ZIP_STORED) 207 part_names.remove("mimetype") 208 except (ValueError, KeyError): 209 printwarn("Missing 'mimetype'") 210 # XML parts 211 for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES: 212 if path not in parts: 213 printwarn(f"Missing '{path}'") 214 continue 215 part = parts[path] 216 if part is None: 217 continue 218 filezip.writestr(path, part) 219 part_names.remove(path) 220 # Everything else 221 for path in part_names: 222 data = parts[path] 223 if data is None: 224 # Deleted 225 continue 226 filezip.writestr(path, data) 227 # Manifest 228 with contextlib.suppress(KeyError): 229 part = parts[ODF_MANIFEST] 230 if part is not None: 231 filezip.writestr(ODF_MANIFEST, part) 232 233 def _save_folder(self, folder: Path | str) -> None: 234 """Save a folder ODF from the available parts.""" 235 236 def dump(part_path: str, content: bytes) -> None: 237 if part_path.endswith("/"): # folder 238 is_folder = True 239 pure_path = PurePath(folder, part_path[:-1]) 240 else: 241 is_folder = False 242 pure_path = PurePath(folder, part_path) 243 path = Path(pure_path) 244 if is_folder: 245 path.mkdir(parents=True, exist_ok=True) 246 else: 247 path.parent.mkdir(parents=True, exist_ok=True) 248 path.write_bytes(content) 249 path.chmod(0o666) 250 251 for part_path, data in self.__parts.items(): 252 if data is None: 253 # Deleted 254 continue 255 dump(part_path, data) 256 257 # Public API 258 259 def get_parts(self) -> list[str]: 260 """Get the list of members.""" 261 if not self.path: 262 # maybe a file like zip archive 263 return list(self.__parts.keys()) 264 if self.__packaging == "zip": 265 parts = [] 266 with ZipFile(self.path) as zf: 267 for name in zf.namelist(): 268 upath = normalize_path(name) 269 parts.append(upath) 270 return parts 271 elif self.__packaging == "folder": 272 return self._get_folder_parts() 273 else: 274 raise ValueError("Unable to provide parts of the document") 275 276 def get_part(self, path: str) -> str | bytes | None: 277 """Get the bytes of a part of the ODF.""" 278 path = str(path) 279 if path in self.__parts: 280 part = self.__parts[path] 281 if part is None: 282 raise ValueError(f'Part "{path}" is deleted') 283 if self.__packaging == "folder": 284 cache_ts = self.__parts_ts.get(path, -1) 285 current_ts = self._get_folder_part_timestamp(path) 286 if current_ts != cache_ts: 287 part, timestamp = self._get_folder_part(path) 288 self.__parts[path] = part 289 self.__parts_ts[path] = timestamp 290 return part 291 if self.__packaging == "zip": 292 return self._get_zip_part(path) 293 if self.__packaging == "folder": 294 part, timestamp = self._get_folder_part(path) 295 self.__parts[path] = part 296 self.__parts_ts[path] = timestamp 297 return part 298 return None 299 300 @property 301 def mimetype(self) -> str: 302 """Return str value of mimetype of the document.""" 303 with contextlib.suppress(Exception): 304 b_mimetype = self.get_part("mimetype") 305 if isinstance(b_mimetype, bytes): 306 return bytes_to_str(b_mimetype) 307 return "" 308 309 @mimetype.setter 310 def mimetype(self, mimetype: str | bytes) -> None: 311 """Set mimetype value of the document.""" 312 if isinstance(mimetype, str): 313 self.__parts["mimetype"] = str_to_bytes(mimetype) 314 elif isinstance(mimetype, bytes): 315 self.__parts["mimetype"] = mimetype 316 else: 317 raise TypeError(f'Wrong mimetype "{mimetype!r}"') 318 319 def set_part(self, path: str, data: bytes) -> None: 320 """Replace or add a new part.""" 321 self.__parts[path] = data 322 323 def del_part(self, path: str) -> None: 324 """Mark a part for deletion.""" 325 self.__parts[path] = None 326 327 @property 328 def clone(self) -> Container: 329 """Make a copy of this container with no path.""" 330 if self.path and self.__packaging == "zip": 331 self._get_all_zip_part() 332 clone = deepcopy(self) 333 clone.path = None 334 return clone 335 336 @staticmethod 337 def _do_backup(target: str | Path) -> None: 338 path = Path(target) 339 if not path.exists(): 340 return 341 back_file = Path(path.stem + ".backup" + path.suffix) 342 if back_file.is_dir(): 343 try: 344 shutil.rmtree(back_file) 345 except OSError as e: 346 printwarn(str(e)) 347 try: 348 shutil.move(target, back_file) 349 except OSError as e: 350 printwarn(str(e)) 351 352 def _save_packaging(self, packaging: str | None) -> str: 353 if not packaging: 354 packaging = self.__packaging if self.__packaging else "zip" 355 packaging = packaging.strip().lower() 356 # if packaging not in ('zip', 'flat', 'folder'): 357 if packaging not in ("zip", "folder"): 358 raise ValueError(f'Packaging of type "{packaging}" is not supported') 359 return packaging 360 361 def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO: 362 if target is None: 363 target = self.path 364 if isinstance(target, Path): 365 target = str(target) 366 if isinstance(target, str): 367 while target.endswith(os.sep): 368 target = target[:-1] 369 while target.endswith(".folder"): 370 target = target.split(".folder", 1)[0] 371 return target # type: ignore 372 373 def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None: 374 if isinstance(target, (str, Path)) and backup: 375 self._do_backup(target) 376 self._save_zip(target) 377 378 def _save_as_folder(self, target: str | Path, backup: bool) -> None: 379 if not isinstance(target, (str, Path)): 380 raise TypeError( 381 f"Saving in folder format requires a folder name, not '{target!r}'" 382 ) 383 if not str(target).endswith(".folder"): 384 target = str(target) + ".folder" 385 if backup: 386 self._do_backup(target) 387 else: 388 path = Path(target) 389 if path.exists(): 390 try: 391 shutil.rmtree(path) 392 except OSError as e: 393 printwarn(str(e)) 394 self._save_folder(target) 395 396 def save( 397 self, 398 target: str | Path | io.BytesIO | None, 399 packaging: str | None = None, 400 backup: bool = False, 401 ) -> None: 402 """Save the container to the given target, a path or a file-like 403 object. 404 405 Package the output document in the same format than current document, 406 unless "packaging" is different. 407 408 Arguments: 409 410 target -- str or file-like or Path 411 412 packaging -- 'zip', or for debugging purpose 'folder' 413 414 backup -- boolean 415 """ 416 parts = self.__parts 417 packaging = self._save_packaging(packaging) 418 # Load parts else they will be considered deleted 419 for path in self.get_parts(): 420 if path not in parts: 421 self.get_part(path) 422 target = self._save_target(target) 423 if packaging == "folder": 424 if isinstance(target, io.BytesIO): 425 raise TypeError( 426 "Impossible to save on io.BytesIO with 'folder' packaging" 427 ) 428 self._save_as_folder(target, backup) 429 else: 430 # default: 431 self._save_as_zip(target, backup)
Representation of the ODF file.
58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path)
70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file) 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile( 78 path_or_file 79 ): 80 self.__packaging = "zip" 81 return self._read_zip() 82 if self.path: 83 is_folder = False 84 with contextlib.suppress(OSError): 85 is_folder = self.path.is_dir() 86 if is_folder: 87 self.__packaging = "folder" 88 return self._read_folder() 89 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
Load the content of an ODF file.
259 def get_parts(self) -> list[str]: 260 """Get the list of members.""" 261 if not self.path: 262 # maybe a file like zip archive 263 return list(self.__parts.keys()) 264 if self.__packaging == "zip": 265 parts = [] 266 with ZipFile(self.path) as zf: 267 for name in zf.namelist(): 268 upath = normalize_path(name) 269 parts.append(upath) 270 return parts 271 elif self.__packaging == "folder": 272 return self._get_folder_parts() 273 else: 274 raise ValueError("Unable to provide parts of the document")
Get the list of members.
276 def get_part(self, path: str) -> str | bytes | None: 277 """Get the bytes of a part of the ODF.""" 278 path = str(path) 279 if path in self.__parts: 280 part = self.__parts[path] 281 if part is None: 282 raise ValueError(f'Part "{path}" is deleted') 283 if self.__packaging == "folder": 284 cache_ts = self.__parts_ts.get(path, -1) 285 current_ts = self._get_folder_part_timestamp(path) 286 if current_ts != cache_ts: 287 part, timestamp = self._get_folder_part(path) 288 self.__parts[path] = part 289 self.__parts_ts[path] = timestamp 290 return part 291 if self.__packaging == "zip": 292 return self._get_zip_part(path) 293 if self.__packaging == "folder": 294 part, timestamp = self._get_folder_part(path) 295 self.__parts[path] = part 296 self.__parts_ts[path] = timestamp 297 return part 298 return None
Get the bytes of a part of the ODF.
300 @property 301 def mimetype(self) -> str: 302 """Return str value of mimetype of the document.""" 303 with contextlib.suppress(Exception): 304 b_mimetype = self.get_part("mimetype") 305 if isinstance(b_mimetype, bytes): 306 return bytes_to_str(b_mimetype) 307 return ""
Return str value of mimetype of the document.
319 def set_part(self, path: str, data: bytes) -> None: 320 """Replace or add a new part.""" 321 self.__parts[path] = data
Replace or add a new part.
323 def del_part(self, path: str) -> None: 324 """Mark a part for deletion.""" 325 self.__parts[path] = None
Mark a part for deletion.
327 @property 328 def clone(self) -> Container: 329 """Make a copy of this container with no path.""" 330 if self.path and self.__packaging == "zip": 331 self._get_all_zip_part() 332 clone = deepcopy(self) 333 clone.path = None 334 return clone
Make a copy of this container with no path.
396 def save( 397 self, 398 target: str | Path | io.BytesIO | None, 399 packaging: str | None = None, 400 backup: bool = False, 401 ) -> None: 402 """Save the container to the given target, a path or a file-like 403 object. 404 405 Package the output document in the same format than current document, 406 unless "packaging" is different. 407 408 Arguments: 409 410 target -- str or file-like or Path 411 412 packaging -- 'zip', or for debugging purpose 'folder' 413 414 backup -- boolean 415 """ 416 parts = self.__parts 417 packaging = self._save_packaging(packaging) 418 # Load parts else they will be considered deleted 419 for path in self.get_parts(): 420 if path not in parts: 421 self.get_part(path) 422 target = self._save_target(target) 423 if packaging == "folder": 424 if isinstance(target, io.BytesIO): 425 raise TypeError( 426 "Impossible to save on io.BytesIO with 'folder' packaging" 427 ) 428 self._save_as_folder(target, backup) 429 else: 430 # default: 431 self._save_as_zip(target, backup)
Save the container to the given target, a path or a file-like object.
Package the output document in the same format than current document, unless "packaging" is different.
Arguments:
target -- str or file-like or Path
packaging -- 'zip', or for debugging purpose 'folder'
backup -- boolean
34class Content(XmlPart): 35 @property 36 def body(self) -> Element: 37 body = self.root.document_body 38 if not isinstance(body, Element): 39 raise ValueError("No body found in document") # noqa:TRY004 40 return body 41 42 # The following two seem useless but they match styles API 43 44 def _get_style_contexts(self, family: str | None) -> tuple: 45 if family == "font-face": 46 return (self.get_element("//office:font-face-decls"),) 47 return ( 48 self.get_element("//office:font-face-decls"), 49 self.get_element("//office:automatic-styles"), 50 ) 51 52 def __str__(self) -> str: 53 return str(self.body) 54 55 # Public API 56 57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result 73 74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Representation of an XML part.
Abstraction of the XML library behind.
57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result
Return the list of styles in the Content part, optionally limited to the given family.
Arguments:
family -- str or None
Return: list of Style
74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: Style or None if not found
Inherited Members
155class Document: 156 """Abstraction of the ODF document. 157 158 To create a new Document, several possibilities: 159 160 - Document() or Document("text") -> an "empty" document of type text 161 - Document("spreadsheet") -> an "empty" document of type spreadsheet 162 - Document("presentation") -> an "empty" document of type presentation 163 - Document("drawing") -> an "empty" document of type drawing 164 165 Meaning of “empty”: these documents are copies of the default 166 templates documents provided with this library, which, as templates, 167 are not really empty. It may be useful to clear the newly created 168 document: document.body.clear(), or adjust meta informations like 169 description or default language: document.meta.set_language('fr-FR') 170 171 If the argument is not a known template type, or is a Path, 172 Document(file) will load the content of the ODF file. 173 174 To explicitly create a document from a custom template, use the 175 Document.new(path) method whose argument is the path to the template file. 176 """ 177 178 def __init__( 179 self, 180 target: str | bytes | Path | Container | io.BytesIO | None = "text", 181 ) -> None: 182 # Cache of XML parts 183 self.__xmlparts: dict[str, XmlPart] = {} 184 # Cache of the body 185 self.__body: Element | None = None 186 self.container: Container | None = None 187 if isinstance(target, bytes): 188 # eager conversion 189 target = bytes_to_str(target) 190 if target is None: 191 # empty document, you probably don't wnat this. 192 self.container = Container() 193 return 194 if isinstance(target, Path): 195 # let's assume we open a container on existing file 196 self.container = Container(target) 197 return 198 if isinstance(target, Container): 199 # special internal case, use an existing container 200 self.container = target 201 return 202 if isinstance(target, str): 203 if target in ODF_TEMPLATES: 204 # assuming a new document from templates 205 self.container = container_from_template(target) 206 return 207 # let's assume we open a container on existing file 208 self.container = Container(target) 209 return 210 if isinstance(target, io.BytesIO): 211 self.container = Container(target) 212 return 213 raise TypeError(f"Unknown Document source type: '{target!r}'") 214 215 def __repr__(self) -> str: 216 return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>" 217 218 def __str__(self) -> str: 219 try: 220 return str(self.get_formatted_text()) 221 except NotImplementedError: 222 return self.body.text_recursive 223 224 @classmethod 225 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 226 """Create a Document from a template. 227 228 The template argument is expected to be the path to a ODF template. 229 230 Arguments: 231 232 template -- str or Path or file-like (io.BytesIO) 233 234 Return : ODF document -- Document 235 """ 236 container = container_from_template(template) 237 return cls(container) 238 239 # Public API 240 241 @property 242 def path(self) -> Path | None: 243 """Shortcut to Document.Container.path.""" 244 if not self.container: 245 return None 246 return self.container.path 247 248 @path.setter 249 def path(self, path_or_str: str | Path) -> None: 250 """Shortcut to Document.Container.path 251 252 Only accepting str or Path.""" 253 if not self.container: 254 return 255 self.container.path = Path(path_or_str) 256 257 def get_parts(self) -> list[str]: 258 """Return available part names with path inside the archive, e.g. 259 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 260 """ 261 if not self.container: 262 raise ValueError("Empty Container") 263 return self.container.get_parts() 264 265 def get_part(self, path: str) -> XmlPart | str | bytes | None: 266 """Return the bytes of the given part. The path is relative to the 267 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 268 269 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 270 to the real path, e.g. content.xml, and return a dedicated object with 271 its own API. 272 273 path formated as URI, so always use '/' separator 274 """ 275 if not self.container: 276 raise ValueError("Empty Container") 277 # "./ObjectReplacements/Object 1" 278 path = path.lstrip("./") 279 path = _get_part_path(path) 280 cls = _get_part_class(path) 281 # Raw bytes 282 if cls is None: 283 return self.container.get_part(path) 284 # XML part 285 part = self.__xmlparts.get(path) 286 if part is None: 287 self.__xmlparts[path] = part = cls(path, self.container) 288 return part 289 290 def set_part(self, path: str, data: bytes) -> None: 291 """Set the bytes of the given part. The path is relative to the 292 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 293 294 path formated as URI, so always use '/' separator 295 """ 296 if not self.container: 297 raise ValueError("Empty Container") 298 # "./ObjectReplacements/Object 1" 299 path = path.lstrip("./") 300 path = _get_part_path(path) 301 cls = _get_part_class(path) 302 # XML part overwritten 303 if cls is not None: 304 with suppress(KeyError): 305 self.__xmlparts[path] 306 self.container.set_part(path, data) 307 308 def del_part(self, path: str) -> None: 309 """Mark a part for deletion. The path is relative to the archive, 310 e.g. "Pictures/1003200258912EB1C3.jpg" 311 """ 312 if not self.container: 313 raise ValueError("Empty Container") 314 path = _get_part_path(path) 315 cls = _get_part_class(path) 316 if path == ODF_MANIFEST or cls is not None: 317 raise ValueError(f"part '{path}' is mandatory") 318 self.container.del_part(path) 319 320 @property 321 def mimetype(self) -> str: 322 if not self.container: 323 raise ValueError("Empty Container") 324 return self.container.mimetype 325 326 @mimetype.setter 327 def mimetype(self, mimetype: str) -> None: 328 if not self.container: 329 raise ValueError("Empty Container") 330 self.container.mimetype = mimetype 331 332 def get_type(self) -> str: 333 """Get the ODF type (also called class) of this document. 334 335 Return: 'chart', 'database', 'formula', 'graphics', 336 'graphics-template', 'image', 'presentation', 337 'presentation-template', 'spreadsheet', 'spreadsheet-template', 338 'text', 'text-master', 'text-template' or 'text-web' 339 """ 340 # The mimetype must be with the form: 341 # application/vnd.oasis.opendocument.text 342 343 # Isolate and return the last part 344 return self.mimetype.rsplit(".", 1)[-1] 345 346 @property 347 def body(self) -> Element: 348 """Return the body element of the content part, where actual content 349 is stored. 350 """ 351 if self.__body is None: 352 self.__body = self.content.body 353 return self.__body 354 355 @property 356 def meta(self) -> Meta: 357 """Return the meta part (meta.xml) of the document, where meta data 358 are stored.""" 359 metadata = self.get_part(ODF_META) 360 if metadata is None or not isinstance(metadata, Meta): 361 raise ValueError("Empty Meta") 362 return metadata 363 364 @property 365 def manifest(self) -> Manifest: 366 """Return the manifest part (manifest.xml) of the document.""" 367 manifest = self.get_part(ODF_MANIFEST) 368 if manifest is None or not isinstance(manifest, Manifest): 369 raise ValueError("Empty Manifest") 370 return manifest 371 372 def _get_formatted_text_footnotes( 373 self, 374 result: list[str], 375 context: dict, 376 rst_mode: bool, 377 ) -> None: 378 # Separate text from notes 379 if rst_mode: 380 result.append("\n") 381 else: 382 result.append("----\n") 383 for citation, body in context["footnotes"]: 384 if rst_mode: 385 result.append(f".. [#] {body}\n") 386 else: 387 result.append(f"[{citation}] {body}\n") 388 # Append a \n after the notes 389 result.append("\n") 390 # Reset for the next paragraph 391 context["footnotes"] = [] 392 393 def _get_formatted_text_annotations( 394 self, 395 result: list[str], 396 context: dict, 397 rst_mode: bool, 398 ) -> None: 399 # Insert the annotations 400 # With a separation 401 if rst_mode: 402 result.append("\n") 403 else: 404 result.append("----\n") 405 for annotation in context["annotations"]: 406 if rst_mode: 407 result.append(f".. [#] {annotation}\n") 408 else: 409 result.append(f"[*] {annotation}\n") 410 context["annotations"] = [] 411 412 def _get_formatted_text_images( 413 self, 414 result: list[str], 415 context: dict, 416 rst_mode: bool, 417 ) -> None: 418 # Insert the images ref, only in rst mode 419 result.append("\n") 420 for ref, filename, (width, height) in context["images"]: 421 result.append(f".. {ref} image:: {filename}\n") 422 if width is not None: 423 result.append(f" :width: {width}\n") 424 if height is not None: 425 result.append(f" :height: {height}\n") 426 context["images"] = [] 427 428 def _get_formatted_text_endnotes( 429 self, 430 result: list[str], 431 context: dict, 432 rst_mode: bool, 433 ) -> None: 434 # Append the end notes 435 if rst_mode: 436 result.append("\n\n") 437 else: 438 result.append("\n========\n") 439 for citation, body in context["endnotes"]: 440 if rst_mode: 441 result.append(f".. [*] {body}\n") 442 else: 443 result.append(f"({citation}) {body}\n") 444 445 def get_formatted_text(self, rst_mode: bool = False) -> str: 446 """Return content as text, with some formatting.""" 447 # For the moment, only "type='text'" 448 doc_type = self.get_type() 449 if doc_type == "spreadsheet": 450 return self._tables_csv() 451 if doc_type in { 452 "text", 453 "text-template", 454 "presentation", 455 "presentation-template", 456 }: 457 return self._formatted_text(rst_mode) 458 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet") 459 460 def _tables_csv(self) -> str: 461 return "\n\n".join(str(table) for table in self.body.get_tables()) 462 463 def _formatted_text(self, rst_mode: bool) -> str: 464 # Initialize an empty context 465 context = { 466 "document": self, 467 "footnotes": [], 468 "endnotes": [], 469 "annotations": [], 470 "rst_mode": rst_mode, 471 "img_counter": 0, 472 "images": [], 473 "no_img_level": 0, 474 } 475 body = self.body 476 # Get the text 477 result = [] 478 for child in body.children: 479 # self._get_formatted_text_child(result, element, context, rst_mode) 480 # if child.tag == "table:table": 481 # result.append(child.get_formatted_text(context)) 482 # return 483 result.append(child.get_formatted_text(context)) 484 if context["footnotes"]: 485 self._get_formatted_text_footnotes(result, context, rst_mode) 486 if context["annotations"]: 487 self._get_formatted_text_annotations(result, context, rst_mode) 488 # Insert the images ref, only in rst mode 489 if context["images"]: 490 self._get_formatted_text_images(result, context, rst_mode) 491 if context["endnotes"]: 492 self._get_formatted_text_endnotes(result, context, rst_mode) 493 return "".join(result) 494 495 def get_formated_meta(self) -> str: 496 """Return meta informations as text, with some formatting.""" 497 result: list[str] = [] 498 499 # Simple values 500 def print_info(name: str, value: Any) -> None: 501 if value: 502 result.append(f"{name}: {value}") 503 504 meta = self.meta 505 print_info("Title", meta.get_title()) 506 print_info("Subject", meta.get_subject()) 507 print_info("Language", meta.get_language()) 508 print_info("Modification date", meta.get_modification_date()) 509 print_info("Creation date", meta.get_creation_date()) 510 print_info("Initial creator", meta.get_initial_creator()) 511 print_info("Keyword", meta.get_keywords()) 512 print_info("Editing duration", meta.get_editing_duration()) 513 print_info("Editing cycles", meta.get_editing_cycles()) 514 print_info("Generator", meta.get_generator()) 515 516 # Statistic 517 result.append("Statistic:") 518 statistic = meta.get_statistic() 519 if statistic: 520 for name, data in statistic.items(): 521 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 522 523 # User defined metadata 524 result.append("User defined metadata:") 525 user_metadata = meta.get_user_defined_metadata() 526 for name, data2 in user_metadata.items(): 527 result.append(f" - {name}: {data2}") 528 529 # And the description 530 print_info("Description", meta.get_description()) 531 532 return "\n".join(result) + "\n" 533 534 def add_file(self, path_or_file: str | Path) -> str: 535 """Insert a file from a path or a file-like object in the container. 536 537 Return the full path to reference in the content. 538 539 Arguments: 540 541 path_or_file -- str or Path or file-like 542 543 Return: str (URI) 544 """ 545 if not self.container: 546 raise ValueError("Empty Container") 547 name = "" 548 # Folder for added files (FIXME hard-coded and copied) 549 manifest = self.manifest 550 medias = manifest.get_paths() 551 # uuid = str(uuid4()) 552 553 if isinstance(path_or_file, (str, Path)): 554 path = Path(path_or_file) 555 extension = path.suffix.lower() 556 name = f"{path.stem}{extension}" 557 if posixpath.join("Pictures", name) in medias: 558 name = f"{path.stem}_{uuid4()}{extension}" 559 else: 560 path = None 561 name = getattr(path_or_file, "name", None) 562 if not name: 563 name = str(uuid4()) 564 media_type, _encoding = guess_type(name) 565 if not media_type: 566 media_type = "application/octet-stream" 567 if manifest.get_media_type("Pictures/") is None: 568 manifest.add_full_path("Pictures/") 569 full_path = posixpath.join("Pictures", name) 570 if path is None: 571 self.container.set_part(full_path, path_or_file.read()) 572 else: 573 self.container.set_part(full_path, path.read_bytes()) 574 manifest.add_full_path(full_path, media_type) 575 return full_path 576 577 @property 578 def clone(self) -> Document: 579 """Return an exact copy of the document. 580 581 Return: Document 582 """ 583 clone = object.__new__(self.__class__) 584 for name in self.__dict__: 585 if name == "_Document__body": 586 setattr(clone, name, None) 587 elif name == "_Document__xmlparts": 588 setattr(clone, name, {}) 589 elif name == "container": 590 if not self.container: 591 raise ValueError("Empty Container") 592 setattr(clone, name, self.container.clone) 593 else: 594 value = deepcopy(getattr(self, name)) 595 setattr(clone, name, value) 596 return clone 597 598 def save( 599 self, 600 target: str | Path | io.BytesIO | None = None, 601 packaging: str = "zip", 602 pretty: bool = False, 603 backup: bool = False, 604 ) -> None: 605 """Save the document, at the same place it was opened or at the given 606 target path. Target can also be a file-like object. It can be saved 607 as a Zip file (default) or as files in a folder (for debugging 608 purpose). XML parts can be pretty printed. 609 610 Arguments: 611 612 target -- str or file-like object 613 614 packaging -- 'zip' or 'folder' 615 616 pretty -- bool 617 618 backup -- bool 619 """ 620 if not self.container: 621 raise ValueError("Empty Container") 622 # Some advertising 623 self.meta.set_generator_default() 624 # Synchronize data with container 625 container = self.container 626 for path, part in self.__xmlparts.items(): 627 if part is not None: 628 container.set_part(path, part.serialize(pretty)) 629 # Save the container 630 container.save(target, packaging=packaging, backup=backup) 631 632 @property 633 def content(self) -> Content: 634 content: Content | None = self.get_part(ODF_CONTENT) # type:ignore 635 if content is None: 636 raise ValueError("Empty Content") 637 return content 638 639 @property 640 def styles(self) -> Styles: 641 styles: Styles | None = self.get_part(ODF_STYLES) # type:ignore 642 if styles is None: 643 raise ValueError("Empty Styles") 644 return styles 645 646 # Styles over several parts 647 648 def get_styles( 649 self, 650 family: str | bytes = "", 651 automatic: bool = False, 652 ) -> list[Style | Element]: 653 # compatibility with old versions: 654 655 if isinstance(family, bytes): 656 family = bytes_to_str(family) 657 return self.content.get_styles(family=family) + self.styles.get_styles( 658 family=family, automatic=automatic 659 ) 660 661 def get_style( 662 self, 663 family: str, 664 name_or_element: str | Style | None = None, 665 display_name: str | None = None, 666 ) -> Style | None: 667 """Return the style uniquely identified by the name/family pair. If 668 the argument is already a style object, it will return it. 669 670 If the name is None, the default style is fetched. 671 672 If the name is not the internal name but the name you gave in a 673 desktop application, use display_name instead. 674 675 Arguments: 676 677 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 678 'number', 'page-layout', 'master-page' 679 680 name -- str or Element or None 681 682 display_name -- str 683 684 Return: Style or None if not found. 685 """ 686 # 1. content.xml 687 element = self.content.get_style( 688 family, name_or_element=name_or_element, display_name=display_name 689 ) 690 if element is not None: 691 return element 692 # 2. styles.xml 693 return self.styles.get_style( 694 family, 695 name_or_element=name_or_element, 696 display_name=display_name, 697 ) 698 699 @staticmethod 700 def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any: 701 if hasattr(style_element, attribute): 702 return getattr(style_element, attribute) 703 return "" 704 705 def _set_automatic_name(self, style: Style, family: str) -> None: 706 """Generate a name for the new automatic style.""" 707 if not hasattr(style, "name"): 708 # do nothing 709 return 710 styles = self.get_styles(family=family, automatic=True) 711 max_index = 0 712 for existing_style in styles: 713 if not hasattr(existing_style, "name"): 714 continue 715 if not existing_style.name.startswith(AUTOMATIC_PREFIX): 716 continue 717 try: 718 index = int(existing_style.name[len(AUTOMATIC_PREFIX) :]) 719 except ValueError: 720 continue 721 max_index = max(max_index, index) 722 723 style.name = f"{AUTOMATIC_PREFIX}{max_index+1}" 724 725 def _insert_style_get_common_styles( 726 self, 727 family: str, 728 name: str, 729 ) -> tuple[Any, Any]: 730 style_container = self.styles.get_element("office:styles") 731 existing = self.styles.get_style(family, name) 732 return existing, style_container 733 734 def _insert_style_get_automatic_styles( 735 self, 736 style: Style, 737 family: str, 738 name: str, 739 ) -> tuple[Any, Any]: 740 style_container = self.content.get_element("office:automatic-styles") 741 # A name ? 742 if name: 743 if hasattr(style, "name"): 744 style.name = name 745 existing = self.content.get_style(family, name) 746 else: 747 self._set_automatic_name(style, family) 748 existing = None 749 return existing, style_container 750 751 def _insert_style_get_default_styles( 752 self, 753 style: Style, 754 family: str, 755 name: str, 756 ) -> tuple[Any, Any]: 757 style_container = self.styles.get_element("office:styles") 758 style.tag = "style:default-style" 759 if name: 760 style.del_attribute("style:name") 761 existing = self.styles.get_style(family) 762 return existing, style_container 763 764 def _insert_style_get_master_page( 765 self, 766 family: str, 767 name: str, 768 ) -> tuple[Any, Any]: 769 style_container = self.styles.get_element("office:master-styles") 770 existing = self.styles.get_style(family, name) 771 return existing, style_container 772 773 def _insert_style_get_font_face_default( 774 self, 775 family: str, 776 name: str, 777 ) -> tuple[Any, Any]: 778 style_container = self.styles.get_element("office:font-face-decls") 779 existing = self.styles.get_style(family, name) 780 return existing, style_container 781 782 def _insert_style_get_font_face( 783 self, 784 family: str, 785 name: str, 786 ) -> tuple[Any, Any]: 787 style_container = self.content.get_element("office:font-face-decls") 788 existing = self.content.get_style(family, name) 789 return existing, style_container 790 791 def _insert_style_get_page_layout( 792 self, 793 family: str, 794 name: str, 795 ) -> tuple[Any, Any]: 796 # force to automatic 797 style_container = self.styles.get_element("office:automatic-styles") 798 existing = self.styles.get_style(family, name) 799 return existing, style_container 800 801 def _insert_style_get_draw_fill_image( 802 self, 803 name: str, 804 ) -> tuple[Any, Any]: 805 # special case for 'draw:fill-image' pseudo style 806 # not family and style_element.__class__.__name__ == "DrawFillImage" 807 style_container = self.styles.get_element("office:styles") 808 existing = self.styles.get_style("", name) 809 return existing, style_container 810 811 def _insert_style_standard( 812 self, 813 style: Style, 814 name: str, 815 family: str, 816 automatic: bool, 817 default: bool, 818 ) -> tuple[Any, Any]: 819 # Common style 820 if name and automatic is False and default is False: 821 return self._insert_style_get_common_styles(family, name) 822 # Automatic style 823 elif automatic is True and default is False: 824 return self._insert_style_get_automatic_styles(style, family, name) 825 # Default style 826 elif automatic is False and default is True: 827 return self._insert_style_get_default_styles(style, family, name) 828 else: 829 raise AttributeError("Invalid combination of arguments") 830 831 def insert_style( # noqa: C901 832 self, 833 style: Style | str, 834 name: str = "", 835 automatic: bool = False, 836 default: bool = False, 837 ) -> Any: 838 """Insert the given style object in the document, as required by the 839 style family and type. 840 841 The style is expected to be a common style with a name. In case it 842 was created with no name, the given can be set on the fly. 843 844 If automatic is True, the style will be inserted as an automatic 845 style. 846 847 If default is True, the style will be inserted as a default style and 848 would replace any existing default style of the same family. Any name 849 or display name would be ignored. 850 851 Automatic and default arguments are mutually exclusive. 852 853 All styles can't be used as default styles. Default styles are 854 allowed for the following families: paragraph, text, section, table, 855 table-column, table-row, table-cell, table-page, chart, drawing-page, 856 graphic, presentation, control and ruby. 857 858 Arguments: 859 860 style -- Style or str 861 862 name -- str 863 864 automatic -- bool 865 866 default -- bool 867 868 Return : style name -- str 869 """ 870 871 # if style is a str, assume it is the Style definition 872 if isinstance(style, str): 873 style_element: Style = Element.from_tag(style) # type: ignore 874 else: 875 style_element = style 876 if not isinstance(style_element, Element): 877 raise TypeError(f"Unknown Style type: '{style!r}'") 878 879 # Get family and name 880 family = self._pseudo_style_attribute(style_element, "family") 881 if not name: 882 name = self._pseudo_style_attribute(style_element, "name") 883 884 # Master page style 885 if family == "master-page": 886 existing, style_container = self._insert_style_get_master_page(family, name) 887 # Font face declarations 888 elif family == "font-face": 889 if default: 890 existing, style_container = self._insert_style_get_font_face_default( 891 family, name 892 ) 893 else: 894 existing, style_container = self._insert_style_get_font_face( 895 family, name 896 ) 897 # page layout style 898 elif family == "page-layout": 899 existing, style_container = self._insert_style_get_page_layout(family, name) 900 # Common style 901 elif family in FAMILY_ODF_STD or family in {"number"}: 902 existing, style_container = self._insert_style_standard( 903 style_element, name, family, automatic, default 904 ) 905 elif not family and style_element.__class__.__name__ == "DrawFillImage": 906 # special case for 'draw:fill-image' pseudo style 907 existing, style_container = self._insert_style_get_draw_fill_image(name) 908 # Invalid style 909 else: 910 raise ValueError( 911 "Invalid style: " 912 f"{style_element}, tag:{style_element.tag}, family:{family}" 913 ) 914 915 # Insert it! 916 if existing is not None: 917 style_container.delete(existing) 918 style_container.append(style_element) 919 return self._pseudo_style_attribute(style_element, "name") 920 921 def get_styled_elements(self, name: str = "") -> list[Element]: 922 """Brute-force to find paragraphs, tables, etc. using the given style 923 name (or all by default). 924 925 Arguments: 926 927 name -- str 928 929 Return: list 930 """ 931 # Header, footer, etc. have styles too 932 return self.content.root.get_styled_elements( 933 name 934 ) + self.styles.root.get_styled_elements(name) 935 936 def show_styles( 937 self, 938 automatic: bool = True, 939 common: bool = True, 940 properties: bool = False, 941 ) -> str: 942 infos = [] 943 for style in self.get_styles(): 944 try: 945 name = style.name # type: ignore 946 except AttributeError: 947 print("--------------") 948 print(style.__class__) 949 print(style.serialize()) 950 raise 951 if style.__class__.__name__ == "DrawFillImage": 952 family = "" 953 else: 954 family = str(style.family) # type: ignore 955 parent = style.parent 956 is_auto = parent and parent.tag == "office:automatic-styles" 957 if is_auto and automatic is False or not is_auto and common is False: 958 continue 959 is_used = bool(self.get_styled_elements(name)) 960 infos.append( 961 { 962 "type": "auto " if is_auto else "common", 963 "used": "y" if is_used else "n", 964 "family": family, 965 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 966 "name": name or "", 967 "display_name": self._pseudo_style_attribute(style, "display_name") 968 or "", 969 "properties": style.get_properties() if properties else None, # type: ignore 970 } 971 ) 972 if not infos: 973 return "" 974 # Sort by family and name 975 infos.sort(key=itemgetter("family", "name")) 976 # Show common and used first 977 infos.sort(key=itemgetter("type", "used"), reverse=True) 978 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 979 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 980 formater = ( 981 "%(type)s used:%(used)s family:%(family)-0" 982 + max_family 983 + "s parent:%(parent)-0" 984 + max_parent 985 + "s name:%(name)s" 986 ) 987 output = [] 988 for info in infos: 989 line = formater % info 990 if info["display_name"]: 991 line += " display_name:" + info["display_name"] # type: ignore 992 output.append(line) 993 if info["properties"]: 994 for name, value in info["properties"].items(): # type: ignore 995 output.append(f" - {name}: {value}") 996 output.append("") 997 return "\n".join(output) 998 999 def delete_styles(self) -> int: 1000 """Remove all style information from content and all styles. 1001 1002 Return: number of deleted styles 1003 """ 1004 # First remove references to styles 1005 for element in self.get_styled_elements(): 1006 for attribute in ( 1007 "text:style-name", 1008 "draw:style-name", 1009 "draw:text-style-name", 1010 "table:style-name", 1011 "style:page-layout-name", 1012 ): 1013 try: 1014 element.del_attribute(attribute) 1015 except KeyError: 1016 continue 1017 # Then remove supposedly orphaned styles 1018 deleted = 0 1019 for style in self.get_styles(): 1020 if style.name is None: # type: ignore 1021 # Don't delete default styles 1022 continue 1023 # elif type(style) is odf_master_page: 1024 # # Don't suppress header and footer, just styling was removed 1025 # continue 1026 style.delete() 1027 deleted += 1 1028 return deleted 1029 1030 def merge_styles_from(self, document: Document) -> None: 1031 """Copy all the styles of a document into ourself. 1032 1033 Styles with the same type and name will be replaced, so only unique 1034 styles will be preserved. 1035 """ 1036 manifest = self.manifest 1037 document_manifest = document.manifest 1038 for style in document.get_styles(): 1039 tagname = style.tag 1040 family = self._pseudo_style_attribute(style, "family") 1041 stylename = style.name # type: ignore 1042 container = style.parent 1043 container_name = container.tag # type: ignore 1044 partname = container.parent.tag # type: ignore 1045 # The destination part 1046 if partname == "office:document-styles": 1047 part: Content | Styles = self.styles 1048 elif partname == "office:document-content": 1049 part = self.content 1050 else: 1051 raise NotImplementedError(partname) 1052 # Implemented containers 1053 if container_name not in { 1054 "office:styles", 1055 "office:automatic-styles", 1056 "office:master-styles", 1057 "office:font-face-decls", 1058 }: 1059 raise NotImplementedError(container_name) 1060 dest = part.get_element(f"//{container_name}") 1061 # Implemented style types 1062 # if tagname not in registered_styles: 1063 # raise NotImplementedError(tagname) 1064 duplicate = part.get_style(family, stylename) 1065 if duplicate is not None: 1066 duplicate.delete() 1067 dest.append(style) 1068 # Copy images from the header/footer 1069 if tagname == "style:master-page": 1070 query = "descendant::draw:image" 1071 for image in style.get_elements(query): 1072 url = image.url # type: ignore 1073 part_url = document.get_part(url) 1074 # Manually add the part to keep the name 1075 self.set_part(url, part_url) # type: ignore 1076 media_type = document_manifest.get_media_type(url) 1077 manifest.add_full_path(url, media_type) # type: ignore 1078 # Copy images from the fill-image 1079 elif tagname == "draw:fill-image": 1080 url = style.url # type: ignore 1081 part_url = document.get_part(url) 1082 self.set_part(url, part_url) # type: ignore 1083 media_type = document_manifest.get_media_type(url) 1084 manifest.add_full_path(url, media_type) # type: ignore 1085 1086 def add_page_break_style(self) -> None: 1087 """Ensure that the document contains the style required for a manual page break. 1088 1089 Then a manual page break can be added to the document with: 1090 from paragraph import PageBreak 1091 ... 1092 document.body.append(PageBreak()) 1093 1094 Note: this style uses the property 'fo:break-after', another 1095 possibility could be the property 'fo:break-before' 1096 """ 1097 if existing := self.get_style( # noqa: SIM102 1098 family="paragraph", 1099 name_or_element="odfdopagebreak", 1100 ): 1101 if properties := existing.get_properties(): # noqa: SIM102 1102 if properties["fo:break-after"] == "page": 1103 return 1104 style = ( 1105 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1106 'style:name="odfdopagebreak">' 1107 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1108 ) 1109 self.insert_style(style, automatic=False)
Abstraction of the ODF document.
To create a new Document, several possibilities:
- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing
Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')
If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.
To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.
178 def __init__( 179 self, 180 target: str | bytes | Path | Container | io.BytesIO | None = "text", 181 ) -> None: 182 # Cache of XML parts 183 self.__xmlparts: dict[str, XmlPart] = {} 184 # Cache of the body 185 self.__body: Element | None = None 186 self.container: Container | None = None 187 if isinstance(target, bytes): 188 # eager conversion 189 target = bytes_to_str(target) 190 if target is None: 191 # empty document, you probably don't wnat this. 192 self.container = Container() 193 return 194 if isinstance(target, Path): 195 # let's assume we open a container on existing file 196 self.container = Container(target) 197 return 198 if isinstance(target, Container): 199 # special internal case, use an existing container 200 self.container = target 201 return 202 if isinstance(target, str): 203 if target in ODF_TEMPLATES: 204 # assuming a new document from templates 205 self.container = container_from_template(target) 206 return 207 # let's assume we open a container on existing file 208 self.container = Container(target) 209 return 210 if isinstance(target, io.BytesIO): 211 self.container = Container(target) 212 return 213 raise TypeError(f"Unknown Document source type: '{target!r}'")
224 @classmethod 225 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 226 """Create a Document from a template. 227 228 The template argument is expected to be the path to a ODF template. 229 230 Arguments: 231 232 template -- str or Path or file-like (io.BytesIO) 233 234 Return : ODF document -- Document 235 """ 236 container = container_from_template(template) 237 return cls(container)
Create a Document from a template.
The template argument is expected to be the path to a ODF template.
Arguments:
template -- str or Path or file-like (io.BytesIO)
Return : ODF document -- Document
241 @property 242 def path(self) -> Path | None: 243 """Shortcut to Document.Container.path.""" 244 if not self.container: 245 return None 246 return self.container.path
Shortcut to Document.Container.path.
257 def get_parts(self) -> list[str]: 258 """Return available part names with path inside the archive, e.g. 259 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 260 """ 261 if not self.container: 262 raise ValueError("Empty Container") 263 return self.container.get_parts()
Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
265 def get_part(self, path: str) -> XmlPart | str | bytes | None: 266 """Return the bytes of the given part. The path is relative to the 267 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 268 269 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 270 to the real path, e.g. content.xml, and return a dedicated object with 271 its own API. 272 273 path formated as URI, so always use '/' separator 274 """ 275 if not self.container: 276 raise ValueError("Empty Container") 277 # "./ObjectReplacements/Object 1" 278 path = path.lstrip("./") 279 path = _get_part_path(path) 280 cls = _get_part_class(path) 281 # Raw bytes 282 if cls is None: 283 return self.container.get_part(path) 284 # XML part 285 part = self.__xmlparts.get(path) 286 if part is None: 287 self.__xmlparts[path] = part = cls(path, self.container) 288 return part
Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.
path formated as URI, so always use '/' separator
290 def set_part(self, path: str, data: bytes) -> None: 291 """Set the bytes of the given part. The path is relative to the 292 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 293 294 path formated as URI, so always use '/' separator 295 """ 296 if not self.container: 297 raise ValueError("Empty Container") 298 # "./ObjectReplacements/Object 1" 299 path = path.lstrip("./") 300 path = _get_part_path(path) 301 cls = _get_part_class(path) 302 # XML part overwritten 303 if cls is not None: 304 with suppress(KeyError): 305 self.__xmlparts[path] 306 self.container.set_part(path, data)
Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
path formated as URI, so always use '/' separator
308 def del_part(self, path: str) -> None: 309 """Mark a part for deletion. The path is relative to the archive, 310 e.g. "Pictures/1003200258912EB1C3.jpg" 311 """ 312 if not self.container: 313 raise ValueError("Empty Container") 314 path = _get_part_path(path) 315 cls = _get_part_class(path) 316 if path == ODF_MANIFEST or cls is not None: 317 raise ValueError(f"part '{path}' is mandatory") 318 self.container.del_part(path)
Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"
332 def get_type(self) -> str: 333 """Get the ODF type (also called class) of this document. 334 335 Return: 'chart', 'database', 'formula', 'graphics', 336 'graphics-template', 'image', 'presentation', 337 'presentation-template', 'spreadsheet', 'spreadsheet-template', 338 'text', 'text-master', 'text-template' or 'text-web' 339 """ 340 # The mimetype must be with the form: 341 # application/vnd.oasis.opendocument.text 342 343 # Isolate and return the last part 344 return self.mimetype.rsplit(".", 1)[-1]
Get the ODF type (also called class) of this document.
Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'
346 @property 347 def body(self) -> Element: 348 """Return the body element of the content part, where actual content 349 is stored. 350 """ 351 if self.__body is None: 352 self.__body = self.content.body 353 return self.__body
Return the body element of the content part, where actual content is stored.
355 @property 356 def meta(self) -> Meta: 357 """Return the meta part (meta.xml) of the document, where meta data 358 are stored.""" 359 metadata = self.get_part(ODF_META) 360 if metadata is None or not isinstance(metadata, Meta): 361 raise ValueError("Empty Meta") 362 return metadata
Return the meta part (meta.xml) of the document, where meta data are stored.
364 @property 365 def manifest(self) -> Manifest: 366 """Return the manifest part (manifest.xml) of the document.""" 367 manifest = self.get_part(ODF_MANIFEST) 368 if manifest is None or not isinstance(manifest, Manifest): 369 raise ValueError("Empty Manifest") 370 return manifest
Return the manifest part (manifest.xml) of the document.
445 def get_formatted_text(self, rst_mode: bool = False) -> str: 446 """Return content as text, with some formatting.""" 447 # For the moment, only "type='text'" 448 doc_type = self.get_type() 449 if doc_type == "spreadsheet": 450 return self._tables_csv() 451 if doc_type in { 452 "text", 453 "text-template", 454 "presentation", 455 "presentation-template", 456 }: 457 return self._formatted_text(rst_mode) 458 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
Return content as text, with some formatting.
495 def get_formated_meta(self) -> str: 496 """Return meta informations as text, with some formatting.""" 497 result: list[str] = [] 498 499 # Simple values 500 def print_info(name: str, value: Any) -> None: 501 if value: 502 result.append(f"{name}: {value}") 503 504 meta = self.meta 505 print_info("Title", meta.get_title()) 506 print_info("Subject", meta.get_subject()) 507 print_info("Language", meta.get_language()) 508 print_info("Modification date", meta.get_modification_date()) 509 print_info("Creation date", meta.get_creation_date()) 510 print_info("Initial creator", meta.get_initial_creator()) 511 print_info("Keyword", meta.get_keywords()) 512 print_info("Editing duration", meta.get_editing_duration()) 513 print_info("Editing cycles", meta.get_editing_cycles()) 514 print_info("Generator", meta.get_generator()) 515 516 # Statistic 517 result.append("Statistic:") 518 statistic = meta.get_statistic() 519 if statistic: 520 for name, data in statistic.items(): 521 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 522 523 # User defined metadata 524 result.append("User defined metadata:") 525 user_metadata = meta.get_user_defined_metadata() 526 for name, data2 in user_metadata.items(): 527 result.append(f" - {name}: {data2}") 528 529 # And the description 530 print_info("Description", meta.get_description()) 531 532 return "\n".join(result) + "\n"
Return meta informations as text, with some formatting.
534 def add_file(self, path_or_file: str | Path) -> str: 535 """Insert a file from a path or a file-like object in the container. 536 537 Return the full path to reference in the content. 538 539 Arguments: 540 541 path_or_file -- str or Path or file-like 542 543 Return: str (URI) 544 """ 545 if not self.container: 546 raise ValueError("Empty Container") 547 name = "" 548 # Folder for added files (FIXME hard-coded and copied) 549 manifest = self.manifest 550 medias = manifest.get_paths() 551 # uuid = str(uuid4()) 552 553 if isinstance(path_or_file, (str, Path)): 554 path = Path(path_or_file) 555 extension = path.suffix.lower() 556 name = f"{path.stem}{extension}" 557 if posixpath.join("Pictures", name) in medias: 558 name = f"{path.stem}_{uuid4()}{extension}" 559 else: 560 path = None 561 name = getattr(path_or_file, "name", None) 562 if not name: 563 name = str(uuid4()) 564 media_type, _encoding = guess_type(name) 565 if not media_type: 566 media_type = "application/octet-stream" 567 if manifest.get_media_type("Pictures/") is None: 568 manifest.add_full_path("Pictures/") 569 full_path = posixpath.join("Pictures", name) 570 if path is None: 571 self.container.set_part(full_path, path_or_file.read()) 572 else: 573 self.container.set_part(full_path, path.read_bytes()) 574 manifest.add_full_path(full_path, media_type) 575 return full_path
Insert a file from a path or a file-like object in the container.
Return the full path to reference in the content.
Arguments:
path_or_file -- str or Path or file-like
Return: str (URI)
577 @property 578 def clone(self) -> Document: 579 """Return an exact copy of the document. 580 581 Return: Document 582 """ 583 clone = object.__new__(self.__class__) 584 for name in self.__dict__: 585 if name == "_Document__body": 586 setattr(clone, name, None) 587 elif name == "_Document__xmlparts": 588 setattr(clone, name, {}) 589 elif name == "container": 590 if not self.container: 591 raise ValueError("Empty Container") 592 setattr(clone, name, self.container.clone) 593 else: 594 value = deepcopy(getattr(self, name)) 595 setattr(clone, name, value) 596 return clone
Return an exact copy of the document.
Return: Document
598 def save( 599 self, 600 target: str | Path | io.BytesIO | None = None, 601 packaging: str = "zip", 602 pretty: bool = False, 603 backup: bool = False, 604 ) -> None: 605 """Save the document, at the same place it was opened or at the given 606 target path. Target can also be a file-like object. It can be saved 607 as a Zip file (default) or as files in a folder (for debugging 608 purpose). XML parts can be pretty printed. 609 610 Arguments: 611 612 target -- str or file-like object 613 614 packaging -- 'zip' or 'folder' 615 616 pretty -- bool 617 618 backup -- bool 619 """ 620 if not self.container: 621 raise ValueError("Empty Container") 622 # Some advertising 623 self.meta.set_generator_default() 624 # Synchronize data with container 625 container = self.container 626 for path, part in self.__xmlparts.items(): 627 if part is not None: 628 container.set_part(path, part.serialize(pretty)) 629 # Save the container 630 container.save(target, packaging=packaging, backup=backup)
Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default) or as files in a folder (for debugging purpose). XML parts can be pretty printed.
Arguments:
target -- str or file-like object
packaging -- 'zip' or 'folder'
pretty -- bool
backup -- bool
648 def get_styles( 649 self, 650 family: str | bytes = "", 651 automatic: bool = False, 652 ) -> list[Style | Element]: 653 # compatibility with old versions: 654 655 if isinstance(family, bytes): 656 family = bytes_to_str(family) 657 return self.content.get_styles(family=family) + self.styles.get_styles( 658 family=family, automatic=automatic 659 )
661 def get_style( 662 self, 663 family: str, 664 name_or_element: str | Style | None = None, 665 display_name: str | None = None, 666 ) -> Style | None: 667 """Return the style uniquely identified by the name/family pair. If 668 the argument is already a style object, it will return it. 669 670 If the name is None, the default style is fetched. 671 672 If the name is not the internal name but the name you gave in a 673 desktop application, use display_name instead. 674 675 Arguments: 676 677 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 678 'number', 'page-layout', 'master-page' 679 680 name -- str or Element or None 681 682 display_name -- str 683 684 Return: Style or None if not found. 685 """ 686 # 1. content.xml 687 element = self.content.get_style( 688 family, name_or_element=name_or_element, display_name=display_name 689 ) 690 if element is not None: 691 return element 692 # 2. styles.xml 693 return self.styles.get_style( 694 family, 695 name_or_element=name_or_element, 696 display_name=display_name, 697 )
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in a desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name -- str or Element or None
display_name -- str
Return: Style or None if not found.
831 def insert_style( # noqa: C901 832 self, 833 style: Style | str, 834 name: str = "", 835 automatic: bool = False, 836 default: bool = False, 837 ) -> Any: 838 """Insert the given style object in the document, as required by the 839 style family and type. 840 841 The style is expected to be a common style with a name. In case it 842 was created with no name, the given can be set on the fly. 843 844 If automatic is True, the style will be inserted as an automatic 845 style. 846 847 If default is True, the style will be inserted as a default style and 848 would replace any existing default style of the same family. Any name 849 or display name would be ignored. 850 851 Automatic and default arguments are mutually exclusive. 852 853 All styles can't be used as default styles. Default styles are 854 allowed for the following families: paragraph, text, section, table, 855 table-column, table-row, table-cell, table-page, chart, drawing-page, 856 graphic, presentation, control and ruby. 857 858 Arguments: 859 860 style -- Style or str 861 862 name -- str 863 864 automatic -- bool 865 866 default -- bool 867 868 Return : style name -- str 869 """ 870 871 # if style is a str, assume it is the Style definition 872 if isinstance(style, str): 873 style_element: Style = Element.from_tag(style) # type: ignore 874 else: 875 style_element = style 876 if not isinstance(style_element, Element): 877 raise TypeError(f"Unknown Style type: '{style!r}'") 878 879 # Get family and name 880 family = self._pseudo_style_attribute(style_element, "family") 881 if not name: 882 name = self._pseudo_style_attribute(style_element, "name") 883 884 # Master page style 885 if family == "master-page": 886 existing, style_container = self._insert_style_get_master_page(family, name) 887 # Font face declarations 888 elif family == "font-face": 889 if default: 890 existing, style_container = self._insert_style_get_font_face_default( 891 family, name 892 ) 893 else: 894 existing, style_container = self._insert_style_get_font_face( 895 family, name 896 ) 897 # page layout style 898 elif family == "page-layout": 899 existing, style_container = self._insert_style_get_page_layout(family, name) 900 # Common style 901 elif family in FAMILY_ODF_STD or family in {"number"}: 902 existing, style_container = self._insert_style_standard( 903 style_element, name, family, automatic, default 904 ) 905 elif not family and style_element.__class__.__name__ == "DrawFillImage": 906 # special case for 'draw:fill-image' pseudo style 907 existing, style_container = self._insert_style_get_draw_fill_image(name) 908 # Invalid style 909 else: 910 raise ValueError( 911 "Invalid style: " 912 f"{style_element}, tag:{style_element.tag}, family:{family}" 913 ) 914 915 # Insert it! 916 if existing is not None: 917 style_container.delete(existing) 918 style_container.append(style_element) 919 return self._pseudo_style_attribute(style_element, "name")
Insert the given style object in the document, as required by the style family and type.
The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.
If automatic is True, the style will be inserted as an automatic style.
If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.
Automatic and default arguments are mutually exclusive.
All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.
Arguments:
style -- Style or str
name -- str
automatic -- bool
default -- bool
Return : style name -- str
921 def get_styled_elements(self, name: str = "") -> list[Element]: 922 """Brute-force to find paragraphs, tables, etc. using the given style 923 name (or all by default). 924 925 Arguments: 926 927 name -- str 928 929 Return: list 930 """ 931 # Header, footer, etc. have styles too 932 return self.content.root.get_styled_elements( 933 name 934 ) + self.styles.root.get_styled_elements(name)
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
936 def show_styles( 937 self, 938 automatic: bool = True, 939 common: bool = True, 940 properties: bool = False, 941 ) -> str: 942 infos = [] 943 for style in self.get_styles(): 944 try: 945 name = style.name # type: ignore 946 except AttributeError: 947 print("--------------") 948 print(style.__class__) 949 print(style.serialize()) 950 raise 951 if style.__class__.__name__ == "DrawFillImage": 952 family = "" 953 else: 954 family = str(style.family) # type: ignore 955 parent = style.parent 956 is_auto = parent and parent.tag == "office:automatic-styles" 957 if is_auto and automatic is False or not is_auto and common is False: 958 continue 959 is_used = bool(self.get_styled_elements(name)) 960 infos.append( 961 { 962 "type": "auto " if is_auto else "common", 963 "used": "y" if is_used else "n", 964 "family": family, 965 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 966 "name": name or "", 967 "display_name": self._pseudo_style_attribute(style, "display_name") 968 or "", 969 "properties": style.get_properties() if properties else None, # type: ignore 970 } 971 ) 972 if not infos: 973 return "" 974 # Sort by family and name 975 infos.sort(key=itemgetter("family", "name")) 976 # Show common and used first 977 infos.sort(key=itemgetter("type", "used"), reverse=True) 978 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 979 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 980 formater = ( 981 "%(type)s used:%(used)s family:%(family)-0" 982 + max_family 983 + "s parent:%(parent)-0" 984 + max_parent 985 + "s name:%(name)s" 986 ) 987 output = [] 988 for info in infos: 989 line = formater % info 990 if info["display_name"]: 991 line += " display_name:" + info["display_name"] # type: ignore 992 output.append(line) 993 if info["properties"]: 994 for name, value in info["properties"].items(): # type: ignore 995 output.append(f" - {name}: {value}") 996 output.append("") 997 return "\n".join(output)
999 def delete_styles(self) -> int: 1000 """Remove all style information from content and all styles. 1001 1002 Return: number of deleted styles 1003 """ 1004 # First remove references to styles 1005 for element in self.get_styled_elements(): 1006 for attribute in ( 1007 "text:style-name", 1008 "draw:style-name", 1009 "draw:text-style-name", 1010 "table:style-name", 1011 "style:page-layout-name", 1012 ): 1013 try: 1014 element.del_attribute(attribute) 1015 except KeyError: 1016 continue 1017 # Then remove supposedly orphaned styles 1018 deleted = 0 1019 for style in self.get_styles(): 1020 if style.name is None: # type: ignore 1021 # Don't delete default styles 1022 continue 1023 # elif type(style) is odf_master_page: 1024 # # Don't suppress header and footer, just styling was removed 1025 # continue 1026 style.delete() 1027 deleted += 1 1028 return deleted
Remove all style information from content and all styles.
Return: number of deleted styles
1030 def merge_styles_from(self, document: Document) -> None: 1031 """Copy all the styles of a document into ourself. 1032 1033 Styles with the same type and name will be replaced, so only unique 1034 styles will be preserved. 1035 """ 1036 manifest = self.manifest 1037 document_manifest = document.manifest 1038 for style in document.get_styles(): 1039 tagname = style.tag 1040 family = self._pseudo_style_attribute(style, "family") 1041 stylename = style.name # type: ignore 1042 container = style.parent 1043 container_name = container.tag # type: ignore 1044 partname = container.parent.tag # type: ignore 1045 # The destination part 1046 if partname == "office:document-styles": 1047 part: Content | Styles = self.styles 1048 elif partname == "office:document-content": 1049 part = self.content 1050 else: 1051 raise NotImplementedError(partname) 1052 # Implemented containers 1053 if container_name not in { 1054 "office:styles", 1055 "office:automatic-styles", 1056 "office:master-styles", 1057 "office:font-face-decls", 1058 }: 1059 raise NotImplementedError(container_name) 1060 dest = part.get_element(f"//{container_name}") 1061 # Implemented style types 1062 # if tagname not in registered_styles: 1063 # raise NotImplementedError(tagname) 1064 duplicate = part.get_style(family, stylename) 1065 if duplicate is not None: 1066 duplicate.delete() 1067 dest.append(style) 1068 # Copy images from the header/footer 1069 if tagname == "style:master-page": 1070 query = "descendant::draw:image" 1071 for image in style.get_elements(query): 1072 url = image.url # type: ignore 1073 part_url = document.get_part(url) 1074 # Manually add the part to keep the name 1075 self.set_part(url, part_url) # type: ignore 1076 media_type = document_manifest.get_media_type(url) 1077 manifest.add_full_path(url, media_type) # type: ignore 1078 # Copy images from the fill-image 1079 elif tagname == "draw:fill-image": 1080 url = style.url # type: ignore 1081 part_url = document.get_part(url) 1082 self.set_part(url, part_url) # type: ignore 1083 media_type = document_manifest.get_media_type(url) 1084 manifest.add_full_path(url, media_type) # type: ignore
Copy all the styles of a document into ourself.
Styles with the same type and name will be replaced, so only unique styles will be preserved.
1086 def add_page_break_style(self) -> None: 1087 """Ensure that the document contains the style required for a manual page break. 1088 1089 Then a manual page break can be added to the document with: 1090 from paragraph import PageBreak 1091 ... 1092 document.body.append(PageBreak()) 1093 1094 Note: this style uses the property 'fo:break-after', another 1095 possibility could be the property 'fo:break-before' 1096 """ 1097 if existing := self.get_style( # noqa: SIM102 1098 family="paragraph", 1099 name_or_element="odfdopagebreak", 1100 ): 1101 if properties := existing.get_properties(): # noqa: SIM102 1102 if properties["fo:break-after"] == "page": 1103 return 1104 style = ( 1105 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1106 'style:name="odfdopagebreak">' 1107 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1108 ) 1109 self.insert_style(style, automatic=False)
Ensure that the document contains the style required for a manual page break.
Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())
Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'
85class DrawFillImage(DrawImage): 86 _tag = "draw:fill-image" 87 _properties: tuple[PropDef, ...] = ( 88 PropDef("display_name", "draw:display-name"), 89 PropDef("name", "draw:name"), 90 PropDef("height", "svg:height"), 91 PropDef("width", "svg:width"), 92 ) 93 94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"
Arguments:
name -- str
display_name -- str
height -- str
width -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
315class DrawGroup(Element, AnchorMix, ZMix, PosMix): 316 """The DrawGroup "draw:g" element represents a group of drawing shapes. 317 318 Warning: implementation is currently minimal. 319 320 Drawing shapes contained by a "draw:g" element that is itself 321 contained by a "draw:a" element, act as hyperlinks using the 322 xlink:href attribute of the containing "draw:a" element. If the 323 included drawing shapes are themselves contained within "draw:a" 324 elements, then the xlink:href attributes of those "draw:a" elements 325 act as the hyperlink information for the shapes they contain. 326 327 The "draw:g" element has the following attributes: draw:caption-id, 328 draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, 329 presentation:class-names, presentation:style-name, svg:y, 330 table:end-cell-address, table:end-x, table:end-y, 331 table:table-background, text:anchor-page-number, text:anchor-type, 332 and xml:id. 333 334 The "draw:g" element has the following child elements: "dr3d:scene", 335 "draw:a", "draw:caption", "draw:circle", "draw:connector", 336 "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", 337 "draw:g", "draw:glue-point", "draw:line", "draw:measure", 338 "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", 339 "draw:rect", "draw:regular-polygon", "office:event-listeners", 340 "svg:desc" and "svg:title". 341 """ 342 343 _tag = "draw:g" 344 _properties: tuple[PropDef, ...] = ( 345 PropDef("draw_id", "draw:id"), 346 PropDef("caption_id", "draw:caption-id"), 347 PropDef("draw_class_names", "draw:class-names"), 348 PropDef("name", "draw:name"), 349 PropDef("style", "draw:style-name"), 350 # ('z_index', 'draw:z-index'), 351 PropDef("presentation_class_names", "presentation:class-names"), 352 PropDef("presentation_style", "presentation:style-name"), 353 PropDef("table_end_cell", "table:end-cell-address"), 354 PropDef("table_end_x", "table:end-x"), 355 PropDef("table_end_y", "table:end-y"), 356 PropDef("table_background", "table:table-background"), 357 # ('anchor_page', 'text:anchor-page-number'), 358 # ('anchor_type', 'text:anchor-type'), 359 PropDef("xml_id", "xml:id"), 360 PropDef("pos_x", "svg:x"), 361 PropDef("pos_y", "svg:y"), 362 ) 363 364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
The DrawGroup "draw:g" element represents a group of drawing shapes.
Warning: implementation is currently minimal.
Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.
The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.
The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".
364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.ZMix
- z_index
- odfdo.frame.PosMix
- position
31class DrawImage(Element): 32 """The "draw:image" element represents an image. An image can be 33 either: 34 - A link to an external resource or 35 - Embedded in the document (Not implemented in this version) 36 37 Warning: image elements must be stored in a frame "draw:frame", 38 see Frame(). 39 """ 40 41 _tag = "draw:image" 42 _properties: tuple[PropDef, ...] = ( 43 PropDef("url", "xlink:href"), 44 PropDef("type", "xlink:type"), 45 PropDef("show", "xlink:show"), 46 PropDef("actuate", "xlink:actuate"), 47 PropDef("filter_name", "draw:filter-name"), 48 ) 49 50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
Initialisation of an DrawImage.
Arguments:
url -- str
type -- str
show -- str
actuate -- str
filter_name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
33class DrawPage(Element): 34 """ODF draw page "draw:page", for pages of presentation and drawings.""" 35 36 _tag = "draw:page" 37 _properties = ( 38 PropDef("name", "draw:name"), 39 PropDef("draw_id", "draw:id"), 40 PropDef("master_page", "draw:master-page-name"), 41 PropDef( 42 "presentation_page_layout", "presentation:presentation-page-layout-name" 43 ), 44 PropDef("style", "draw:style-name"), 45 ) 46 47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style 81 82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page) 105 106 def get_shapes(self) -> list[Element]: 107 query = "(descendant::" + "|descendant::".join(registered_shapes) + ")" 108 return self.get_elements(query) 109 110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
ODF draw page "draw:page", for pages of presentation and drawings.
47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style
Arguments:
draw_id -- str
name -- str
master_page -- str
presentation_page_layout -- str
style -- str
82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page)
110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
306class Element(CachedElement): 307 """Super class of all ODF classes. 308 309 Representation of an XML element. Abstraction of the XML library behind. 310 """ 311 312 _tag: str = "" 313 _caching: bool = False 314 _properties: tuple[PropDef, ...] = () 315 316 def __init__(self, **kwargs: Any) -> None: 317 tag_or_elem = kwargs.pop("tag_or_elem", None) 318 if tag_or_elem is None: 319 # Instance for newly created object: create new lxml element and 320 # continue by subclass __init__ 321 # If the tag key word exists, make a custom element 322 self._do_init = True 323 tag = kwargs.pop("tag", self._tag) 324 self.__element = self.make_etree_element(tag) 325 else: 326 # called with an existing lxml element, sould be a result of 327 # from_tag() casting, do not execute the subclass __init__ 328 if not isinstance(tag_or_elem, _Element): 329 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 330 self._do_init = False 331 self.__element = tag_or_elem 332 333 def __repr__(self) -> str: 334 return f"<{self.__class__.__name__} tag={self.tag}>" 335 336 def __str__(self) -> str: 337 return self.text_recursive 338 339 @classmethod 340 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 341 """Element class and subclass factory. 342 343 Turn an lxml Element or ODF string tag into an ODF XML Element 344 of the relevant class. 345 346 Arguments: 347 348 tag_or_elem -- ODF str tag or lxml.Element 349 350 Return: Element (or subclass) instance 351 """ 352 if isinstance(tag_or_elem, str): 353 # assume the argument is a prefix:name tag 354 elem = cls.make_etree_element(tag_or_elem) 355 else: 356 elem = tag_or_elem 357 klass = _class_registry.get(elem.tag, cls) 358 return klass(tag_or_elem=elem) 359 360 @classmethod 361 def from_tag_for_clone( 362 cls: type, 363 tree_element: _Element, 364 cache: tuple | None, 365 ) -> Element: 366 tag = to_str(tree_element.tag) 367 klass = _class_registry.get(tag, cls) 368 element = klass(tag_or_elem=tree_element) 369 if cache and element._caching: 370 element._tmap = cache[0] 371 element._cmap = cache[1] 372 if len(cache) == 3: 373 element._rmap = cache[2] 374 return element 375 376 @staticmethod 377 def make_etree_element(tag: str) -> _Element: 378 if not isinstance(tag, str): 379 raise TypeError(f"Tag is not str: {tag!r}") 380 tag = tag.strip() 381 if not tag: 382 raise ValueError("Tag is empty") 383 if "<" not in tag: 384 # Qualified name 385 # XXX don't build the element from scratch or lxml will pollute with 386 # repeated namespace declarations 387 tag = f"<{tag}/>" 388 # XML fragment 389 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 390 return root[0] 391 392 @staticmethod 393 def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable: 394 name = _get_lxml_tag(attr_name) 395 396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value) 408 409 return getter 410 411 @staticmethod 412 def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable: 413 name = _get_lxml_tag(attr_name) 414 415 def setter(self: Element, value: Any) -> None: 416 try: 417 if family and self.family != family: # type: ignore 418 return None 419 except AttributeError: 420 return None 421 if value is None: 422 with contextlib.suppress(KeyError): 423 del self.__element.attrib[name] 424 return 425 if isinstance(value, bool): 426 value = Boolean.encode(value) 427 self.__element.set(name, str(value)) 428 429 return setter 430 431 @classmethod 432 def _define_attribut_property(cls: type[Element]) -> None: 433 for prop in cls._properties: 434 setattr( 435 cls, 436 prop.name, 437 property( 438 cls._generic_attrib_getter(prop.attr, prop.family or None), 439 cls._generic_attrib_setter(prop.attr, prop.family or None), 440 None, 441 f"Get/set the attribute {prop.attr}", 442 ), 443 ) 444 445 @staticmethod 446 def _make_before_regex( 447 before: str | None, 448 after: str | None, 449 ) -> re.Pattern: 450 # 1) before xor after is not None 451 if before is not None: 452 return re.compile(before) 453 else: 454 if after is None: 455 raise ValueError("Both 'before' and 'after' are None") 456 return re.compile(after) 457 458 @staticmethod 459 def _search_negative_position( 460 xpath_result: list, 461 regex: re.Pattern, 462 ) -> tuple[str, re.Match]: 463 # Found the last text that matches the regex 464 text = None 465 for a_text in xpath_result: 466 if regex.search(str(a_text)) is not None: 467 text = a_text 468 if text is None: 469 raise ValueError(f"Text not found: '{xpath_result}'") 470 if not isinstance(text, str): 471 raise TypeError(f"Text not found or text not of type str: '{text}'") 472 return text, list(regex.finditer(text))[-1] 473 474 @staticmethod 475 def _search_positive_position( 476 xpath_result: list, 477 regex: re.Pattern, 478 position: int, 479 ) -> tuple[str, re.Match]: 480 # Found the last text that matches the regex 481 count = 0 482 for text in xpath_result: 483 found_nb = len(regex.findall(str(text))) 484 if found_nb + count >= position + 1: 485 break 486 count += found_nb 487 else: 488 raise ValueError(f"Text not found: '{xpath_result}'") 489 if not isinstance(text, str): 490 raise TypeError(f"Text not found or text not of type str: '{text}'") 491 return text, list(regex.finditer(text))[position - count] 492 493 def _insert_before_after( 494 self, 495 current: _Element, 496 element: _Element, 497 before: str | None, 498 after: str | None, 499 position: int, 500 xpath_text: XPath, 501 ) -> tuple[int, str]: 502 regex = self._make_before_regex(before, after) 503 xpath_result = xpath_text(current) 504 if not isinstance(xpath_result, list): 505 raise TypeError("Bad XPath result") 506 # position = -1 507 if position < 0: 508 text, sre = self._search_negative_position(xpath_result, regex) 509 # position >= 0 510 else: 511 text, sre = self._search_positive_position(xpath_result, regex, position) 512 # Compute pos 513 if before is None: 514 pos = sre.end() 515 else: 516 pos = sre.start() 517 return pos, text 518 519 def _insert_find_text( 520 self, 521 current: _Element, 522 element: _Element, 523 before: str | None, 524 after: str | None, 525 position: int, 526 xpath_text: XPath, 527 ) -> tuple[int, str]: 528 # Find the text 529 xpath_result = xpath_text(current) 530 if not isinstance(xpath_result, list): 531 raise TypeError("Bad XPath result") 532 count = 0 533 for text in xpath_result: 534 if not isinstance(text, str): 535 continue 536 found_nb = len(text) 537 if found_nb + count >= position: 538 break 539 count += found_nb 540 else: 541 raise ValueError("Text not found") 542 # We insert before the character 543 pos = position - count 544 return pos, text 545 546 def _insert( 547 self, 548 element: Element, 549 before: str | None = None, 550 after: str | None = None, 551 position: int = 0, 552 main_text: bool = False, 553 ) -> None: 554 """Insert an element before or after the characters in the text which 555 match the regex before/after. 556 557 When the regex matches more of one part of the text, position can be 558 set to choice which part must be used. If before and after are None, 559 we use only position that is the number of characters. If position is 560 positive and before=after=None, we insert before the position 561 character. But if position=-1, we insert after the last character. 562 563 564 Arguments: 565 566 element -- Element 567 568 before -- str regex 569 570 after -- str regex 571 572 position -- int 573 """ 574 # not implemented: if main_text is True, filter out the annotations texts in computation. 575 current = self.__element 576 xelement = element.__element 577 578 if main_text: 579 xpath_text = _xpath_text_main_descendant 580 else: 581 xpath_text = _xpath_text_descendant 582 583 # 1) before xor after is not None 584 if (before is not None) ^ (after is not None): 585 pos, text = self._insert_before_after( 586 current, 587 xelement, 588 before, 589 after, 590 position, 591 xpath_text, 592 ) 593 # 2) before=after=None => only with position 594 elif before is None and after is None: 595 # Hack if position is negative => quickly 596 if position < 0: 597 current.append(xelement) 598 return 599 pos, text = self._insert_find_text( 600 current, 601 xelement, 602 before, 603 after, 604 position, 605 xpath_text, 606 ) 607 else: 608 raise ValueError("bad combination of arguments") 609 610 # Compute new texts 611 text_before = text[:pos] if text[:pos] else None 612 text_after = text[pos:] if text[pos:] else None 613 614 # Insert! 615 parent = text.getparent() # type: ignore 616 if text.is_text: # type: ignore 617 parent.text = text_before 618 element.tail = text_after 619 parent.insert(0, xelement) 620 else: 621 parent.addnext(xelement) 622 parent.tail = text_before 623 element.tail = text_after 624 625 def _insert_between( # noqa: C901 626 self, 627 element: Element, 628 from_: str, 629 to: str, 630 ) -> None: 631 """Insert the given empty element to wrap the text beginning with 632 "from_" and ending with "to". 633 634 Example 1: '<p>toto tata titi</p> 635 636 We want to insert a link around "tata". 637 638 Result 1: '<p>toto <a>tata</a> titi</p> 639 640 Example 2: '<p><span>toto</span> tata titi</p> 641 642 We want to insert a link around "tata". 643 644 Result 2: '<p><span>toto</span> <a>tata</a> titi</p> 645 646 Example 3: '<p>toto <span> tata </span> titi</p>' 647 648 We want to insert a link from "tata" to "titi" included. 649 650 Result 3: '<p>toto <span> </span>' 651 '<a><span>tata </span> titi</a></p>' 652 653 Example 4: '<p>toto <span>tata titi</span> tutu</p>' 654 655 We want to insert a link from "titi" to "tutu" 656 657 Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>' 658 '<a> tutu</a></p>' 659 660 Example 5: '<p>toto <span>tata titi</span> ' 661 '<span>tutu tyty</span></p>' 662 663 We want to insert a link from "titi" to "tutu" 664 665 Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> ' 666 '<a> <span>tutu</span></a><span> tyty</span></p>' 667 """ 668 current = self.__element 669 wrapper = element.__element 670 671 xpath_result = _xpath_text_descendant(current) 672 if not isinstance(xpath_result, list): 673 raise TypeError("Bad XPath result") 674 675 for text in xpath_result: 676 if not isinstance(text, str): 677 raise TypeError("Text not found or text not of type str") 678 if from_ not in text: 679 continue 680 from_index = text.index(from_) 681 text_before = text[:from_index] 682 text_after = text[from_index:] 683 from_container = text.getparent() # type: ignore 684 if not isinstance(from_container, _Element): 685 raise TypeError("Bad XPath result") 686 # Include from_index to match a single word 687 to_index = text.find(to, from_index) 688 if to_index >= 0: 689 # Simple case: "from" and "to" in the same element 690 to_end = to_index + len(to) 691 if text.is_text: # type: ignore 692 from_container.text = text_before 693 wrapper.text = text[to_index:to_end] 694 wrapper.tail = text[to_end:] 695 from_container.insert(0, wrapper) 696 else: 697 from_container.tail = text_before 698 wrapper.text = text[to_index:to_end] 699 wrapper.tail = text[to_end:] 700 parent = from_container.getparent() 701 index = parent.index(from_container) # type: ignore 702 parent.insert(index + 1, wrapper) # type: ignore 703 return 704 else: 705 # Exit to the second part where we search for the end text 706 break 707 else: 708 raise ValueError("Start text not found") 709 710 # The container is split in two 711 container2 = deepcopy(from_container) 712 if text.is_text: # type: ignore 713 from_container.text = text_before 714 from_container.tail = None 715 container2.text = text_after 716 from_container.tail = None 717 else: 718 from_container.tail = text_before 719 container2.tail = text_after 720 # Stack the copy into the surrounding element 721 wrapper.append(container2) 722 parent = from_container.getparent() 723 index = parent.index(from_container) # type: ignore 724 parent.insert(index + 1, wrapper) # type: ignore 725 726 xpath_result = _xpath_text_descendant(wrapper) 727 if not isinstance(xpath_result, list): 728 raise TypeError("Bad XPath result") 729 730 for text in xpath_result: 731 if not isinstance(text, str): 732 raise TypeError("Text not found or text not of type str") 733 if to not in text: 734 continue 735 to_end = text.index(to) + len(to) 736 text_before = text[:to_end] 737 text_after = text[to_end:] 738 container_to = text.getparent() # type: ignore 739 if not isinstance(container_to, _Element): 740 raise TypeError("Bad XPath result") 741 if text.is_text: # type: ignore 742 container_to.text = text_before 743 container_to.tail = text_after 744 else: 745 container_to.tail = text_before 746 next_one = container_to.getnext() 747 if next_one is None: 748 next_one = container_to.getparent() 749 next_one.tail = text_after # type: ignore 750 return 751 raise ValueError("End text not found") 752 753 @property 754 def tag(self) -> str: 755 """Get/set the underlying xml tag with the given qualified name. 756 757 Warning: direct change of tag does not change the element class. 758 759 Arguments: 760 761 qname -- str (e.g. "text:span") 762 """ 763 return _get_prefixed_name(self.__element.tag) 764 765 @tag.setter 766 def tag(self, qname: str) -> None: 767 self.__element.tag = _get_lxml_tag(qname) 768 769 def elements_repeated_sequence( 770 self, 771 xpath_instance: XPath, 772 name: str, 773 ) -> list[tuple[int, int]]: 774 """Utility method for table module.""" 775 lxml_tag = _get_lxml_tag_or_name(name) 776 element = self.__element 777 sub_elements = xpath_instance(element) 778 if not isinstance(sub_elements, list): 779 raise TypeError("Bad XPath result.") 780 result: list[tuple[int, int]] = [] 781 idx = -1 782 for sub_element in sub_elements: 783 if not isinstance(sub_element, _Element): 784 continue 785 idx += 1 786 value = sub_element.get(lxml_tag) 787 if value is None: 788 result.append((idx, 1)) 789 continue 790 try: 791 int_value = int(value) 792 except ValueError: 793 int_value = 1 794 result.append((idx, max(int_value, 1))) 795 return result 796 797 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 798 cache: tuple | None = None 799 element = self.__element 800 if isinstance(xpath_query, str): 801 new_xpath_query = xpath_compile(xpath_query) 802 result = new_xpath_query(element) 803 else: 804 result = xpath_query(element) 805 if not isinstance(result, list): 806 raise TypeError("Bad XPath result") 807 808 if hasattr(self, "_tmap"): 809 if hasattr(self, "_rmap"): 810 cache = (self._tmap, self._cmap, self._rmap) 811 else: 812 cache = (self._tmap, self._cmap) 813 return [ 814 Element.from_tag_for_clone(e, cache) 815 for e in result 816 if isinstance(e, _Element) 817 ] 818 819 # fixme : need original get_element as wrapper of get_elements 820 821 def get_element(self, xpath_query: XPath | str) -> Element | None: 822 element = self.__element 823 result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES) 824 if result: 825 return Element.from_tag(result[0]) # type:ignore 826 return None 827 828 def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None: 829 element = self.__element 830 result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES) 831 if result: 832 return Element.from_tag(result[0]) # type:ignore 833 return None 834 835 def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None: 836 element = self.__element 837 result = xpath_instance(element, idx=idx + 1) 838 if result: 839 return Element.from_tag(result[0]) # type:ignore 840 return None 841 842 @property 843 def attributes(self) -> dict[str, str]: 844 return { 845 _get_prefixed_name(str(key)): str(value) 846 for key, value in self.__element.attrib.items() 847 } 848 849 def get_attribute(self, name: str) -> str | bool | None: 850 """Return the attribute value as type str | bool | None.""" 851 element = self.__element 852 lxml_tag = _get_lxml_tag_or_name(name) 853 value = element.get(lxml_tag) 854 if value is None: 855 return None 856 elif value in ("true", "false"): 857 return Boolean.decode(value) 858 return str(value) 859 860 def get_attribute_integer(self, name: str) -> int | None: 861 """Return either the attribute as type int, or None.""" 862 element = self.__element 863 lxml_tag = _get_lxml_tag_or_name(name) 864 value = element.get(lxml_tag) 865 if value is None: 866 return None 867 try: 868 return int(value) 869 except ValueError: 870 return None 871 872 def get_attribute_string(self, name: str) -> str | None: 873 """Return either the attribute as type str, or None.""" 874 element = self.__element 875 lxml_tag = _get_lxml_tag_or_name(name) 876 value = element.get(lxml_tag) 877 if value is None: 878 return None 879 return str(value) 880 881 def set_attribute( 882 self, name: str, value: bool | str | tuple[int, int, int] | None 883 ) -> None: 884 if name in ODF_COLOR_PROPERTY: 885 if isinstance(value, bool): 886 raise TypeError(f"Wrong color type {value!r}") 887 if value != "transparent": 888 value = hexa_color(value) 889 element = self.__element 890 lxml_tag = _get_lxml_tag_or_name(name) 891 if isinstance(value, bool): 892 value = Boolean.encode(value) 893 elif value is None: 894 with contextlib.suppress(KeyError): 895 del element.attrib[lxml_tag] 896 return 897 element.set(lxml_tag, str(value)) 898 899 def set_style_attribute(self, name: str, value: Element | str) -> None: 900 """Shortcut to accept a style object as a value.""" 901 if isinstance(value, Element): 902 value = str(value.name) # type:ignore 903 return self.set_attribute(name, value) 904 905 def del_attribute(self, name: str) -> None: 906 element = self.__element 907 lxml_tag = _get_lxml_tag_or_name(name) 908 del element.attrib[lxml_tag] 909 910 @property 911 def text(self) -> str: 912 """Get / set the text content of the element.""" 913 return self.__element.text or "" 914 915 @text.setter 916 def text(self, text: str | None) -> None: 917 if text is None: 918 text = "" 919 try: 920 self.__element.text = text 921 except TypeError as e: 922 raise TypeError(f'Str type expected: "{type(text)}"') from e 923 924 @property 925 def text_recursive(self) -> str: 926 return "".join(str(x) for x in self.__element.itertext()) 927 928 @property 929 def tail(self) -> str | None: 930 """Get / set the text immediately following the element.""" 931 return self.__element.tail 932 933 @tail.setter 934 def tail(self, text: str | None) -> None: 935 self.__element.tail = text or "" 936 937 def search(self, pattern: str) -> int | None: 938 """Return the first position of the pattern in the text content of 939 the element, or None if not found. 940 941 Python regular expression syntax applies. 942 943 Arguments: 944 945 pattern -- str 946 947 Return: int or None 948 """ 949 match = re.search(pattern, self.text_recursive) 950 if match is None: 951 return None 952 return match.start() 953 954 def match(self, pattern: str) -> bool: 955 """return True if the pattern is found one or more times anywhere in 956 the text content of the element. 957 958 Python regular expression syntax applies. 959 960 Arguments: 961 962 pattern -- str 963 964 Return: bool 965 """ 966 return self.search(pattern) is not None 967 968 def replace(self, pattern: str, new: str | None = None) -> int: 969 """Replace the pattern with the given text, or delete if text is an 970 empty string, and return the number of replacements. By default, only 971 return the number of occurences that would be replaced. 972 973 It cannot replace patterns found across several element, like a word 974 split into two consecutive spans. 975 976 Python regular expression syntax applies. 977 978 Arguments: 979 980 pattern -- str 981 982 new -- str 983 984 Return: int 985 """ 986 if not isinstance(pattern, str): 987 # Fail properly if the pattern is an non-ascii bytestring 988 pattern = str(pattern) 989 cpattern = re.compile(pattern) 990 count = 0 991 for text in self.xpath("descendant::text()"): 992 if new is None: 993 count += len(cpattern.findall(str(text))) 994 else: 995 new_text, number = cpattern.subn(new, str(text)) 996 container = text.parent 997 if text.is_text(): # type: ignore 998 container.text = new_text # type: ignore 999 else: 1000 container.tail = new_text # type: ignore 1001 count += number 1002 return count 1003 1004 @property 1005 def root(self) -> Element: 1006 element = self.__element 1007 tree = element.getroottree() 1008 root = tree.getroot() 1009 return Element.from_tag(root) 1010 1011 @property 1012 def parent(self) -> Element | None: 1013 element = self.__element 1014 parent = element.getparent() 1015 if parent is None: 1016 # Already at root 1017 return None 1018 return Element.from_tag(parent) 1019 1020 @property 1021 def is_bound(self) -> bool: 1022 return self.parent is not None 1023 1024 # def get_next_sibling(self): 1025 # element = self.__element 1026 # next_one = element.getnext() 1027 # if next_one is None: 1028 # return None 1029 # return Element.from_tag(next_one) 1030 # 1031 # def get_prev_sibling(self): 1032 # element = self.__element 1033 # prev = element.getprevious() 1034 # if prev is None: 1035 # return None 1036 # return Element.from_tag(prev) 1037 1038 @property 1039 def children(self) -> list[Element]: 1040 element = self.__element 1041 return [ 1042 Element.from_tag(e) 1043 for e in element.iterchildren() 1044 if isinstance(e, _Element) 1045 ] 1046 1047 def index(self, child: Element) -> int: 1048 """Return the position of the child in this element. 1049 1050 Inspired by lxml 1051 """ 1052 return self.__element.index(child.__element) 1053 1054 @property 1055 def text_content(self) -> str: 1056 """Get / set the text of the embedded paragraph, including embeded 1057 annotations, cells... 1058 1059 Set create a paragraph if missing 1060 """ 1061 return "\n".join( 1062 child.text_recursive for child in self.get_elements("descendant::text:p") 1063 ) 1064 1065 @text_content.setter 1066 def text_content(self, text: str | None) -> None: 1067 paragraphs = self.get_elements("text:p") 1068 if not paragraphs: 1069 # E.g., text:p in draw:text-box in draw:frame 1070 paragraphs = self.get_elements("*/text:p") 1071 if paragraphs: 1072 paragraph = paragraphs.pop(0) 1073 for obsolete in paragraphs: 1074 obsolete.delete() 1075 else: 1076 paragraph = Element.from_tag("text:p") 1077 self.insert(paragraph, FIRST_CHILD) 1078 # As "text_content" returned all text nodes, "text_content" 1079 # will overwrite all text nodes and children that may contain them 1080 element = paragraph.__element 1081 # Clear but the attributes 1082 del element[:] 1083 element.text = text 1084 1085 def _erase_text_content(self) -> None: 1086 paragraphs = self.get_elements("text:p") 1087 if not paragraphs: 1088 # E.g., text:p in draw:text-box in draw:frame 1089 paragraphs = self.get_elements("*/text:p") 1090 if paragraphs: 1091 paragraphs.pop(0) 1092 for obsolete in paragraphs: 1093 obsolete.delete() 1094 1095 def is_empty(self) -> bool: 1096 """Check if the element is empty : no text, no children, no tail. 1097 1098 Return: Boolean 1099 """ 1100 element = self.__element 1101 if element.tail is not None: 1102 return False 1103 if element.text is not None: 1104 return False 1105 if list(element.iterchildren()): 1106 return False 1107 return True 1108 1109 def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]: 1110 element = self.__element 1111 next_one = element.getnext() 1112 if next_one is not None: 1113 return Element.from_tag(next_one), target 1114 parent = self.parent 1115 if parent is None: 1116 return None, None 1117 return parent._get_successor(target.parent) # type:ignore 1118 1119 def _get_between_base( # noqa:C901 1120 self, 1121 tag1: Element, 1122 tag2: Element, 1123 ) -> list[Element]: 1124 def find_any_id(elem: Element) -> tuple[str, str, str]: 1125 elem_tag = elem.tag 1126 for attribute in ( 1127 "text:id", 1128 "text:change-id", 1129 "text:name", 1130 "office:name", 1131 "text:ref-name", 1132 "xml:id", 1133 ): 1134 idx = elem.get_attribute(attribute) 1135 if idx is not None: 1136 return elem_tag, attribute, str(idx) 1137 raise ValueError(f"No Id found in {elem.serialize()}") 1138 1139 def common_ancestor( 1140 tag1: str, 1141 attr1: str, 1142 val1: str, 1143 tag2: str, 1144 attr2: str, 1145 val2: str, 1146 ) -> Element | None: 1147 root = self.root 1148 request1 = f'descendant::{tag1}[@{attr1}="{val1}"]' 1149 request2 = f'descendant::{tag2}[@{attr2}="{val2}"]' 1150 ancestor = root.xpath(request1)[0] 1151 if ancestor is None: 1152 return None 1153 while True: 1154 # print "up", 1155 new_ancestor = ancestor.parent 1156 if new_ancestor is None: 1157 return None 1158 has_tag2 = new_ancestor.xpath(request2) 1159 ancestor = new_ancestor 1160 if not has_tag2: 1161 continue 1162 # print 'found' 1163 break 1164 # print up.serialize() 1165 return ancestor 1166 1167 elem1_tag, elem1_attr, elem1_val = find_any_id(tag1) 1168 elem2_tag, elem2_attr, elem2_val = find_any_id(tag2) 1169 ancestor_result = common_ancestor( 1170 elem1_tag, 1171 elem1_attr, 1172 elem1_val, 1173 elem2_tag, 1174 elem2_attr, 1175 elem2_val, 1176 ) 1177 if ancestor_result is None: 1178 raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}") 1179 ancestor = ancestor_result.clone 1180 path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]' 1181 path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]' 1182 result = ancestor.clone 1183 for child in result.children: 1184 result.delete(child) 1185 result.text = "" 1186 result.tail = "" 1187 target = result 1188 current = ancestor.children[0] 1189 1190 state = 0 1191 while True: 1192 if current is None: 1193 raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}") 1194 # print 'current', state, current.serialize() 1195 if state == 0: # before tag 1 1196 if current.xpath(f"descendant-or-self::{path1}"): 1197 if current.xpath(f"self::{path1}"): 1198 tail = current.tail 1199 if tail: 1200 # got a tail => the parent should be either t:p or t:h 1201 target.text = tail # type: ignore 1202 current, target = current._get_successor(target) # type: ignore 1203 state = 1 1204 continue 1205 # got T1 in chidren, need further analysis 1206 new_target = current.clone 1207 for child in new_target.children: 1208 new_target.delete(child) 1209 new_target.text = "" 1210 new_target.tail = "" 1211 target.append(new_target) # type: ignore 1212 target = new_target 1213 current = current.children[0] 1214 continue 1215 else: 1216 # before tag1 : forget element, go to next one 1217 current, target = current._get_successor(target) # type: ignore 1218 continue 1219 elif state == 1: # collect elements 1220 further = False 1221 if current.xpath(f"descendant-or-self::{path2}"): 1222 if current.xpath(f"self::{path2}"): 1223 # end of trip 1224 break 1225 # got T2 in chidren, need further analysis 1226 further = True 1227 # further analysis needed : 1228 if further: 1229 new_target = current.clone 1230 for child in new_target.children: 1231 new_target.delete(child) 1232 new_target.text = "" 1233 new_target.tail = "" 1234 target.append(new_target) # type: ignore 1235 target = new_target 1236 current = current.children[0] 1237 continue 1238 # collect 1239 target.append(current.clone) # type: ignore 1240 current, target = current._get_successor(target) # type: ignore 1241 continue 1242 # Now resu should be the "parent" of inserted parts 1243 # - a text:h or text:p sigle item (simple case) 1244 # - a upper element, with some text:p, text:h in it => need to be 1245 # stripped to have a list of text:p, text:h 1246 if result.tag in {"text:p", "text:h"}: 1247 inner = [result] 1248 else: 1249 inner = result.children 1250 return inner 1251 1252 def get_between( 1253 self, 1254 tag1: Element, 1255 tag2: Element, 1256 as_text: bool = False, 1257 clean: bool = True, 1258 no_header: bool = True, 1259 ) -> list | Element | str: 1260 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1261 be unique and having an id attribute. 1262 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1263 If as_text is True: returns the text content. 1264 If clean is True: suppress unwanted tags (deletions marks, ...) 1265 If no_header is True: existing text:h are changed in text:p 1266 By default: returns a list of Element, cleaned and without headers. 1267 1268 Implementation and standard retrictions: 1269 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1270 of insert tags are: 1271 1272 - any text:h, text:p or sub tag of these 1273 1274 - some text, part of a parent text:h or text:p 1275 1276 Arguments: 1277 1278 tag1 -- Element 1279 1280 tag2 -- Element 1281 1282 as_text -- boolean 1283 1284 clean -- boolean 1285 1286 no_header -- boolean 1287 1288 Return: list of odf_paragraph or odf_header 1289 """ 1290 inner = self._get_between_base(tag1, tag2) 1291 1292 if clean: 1293 clean_tags = ( 1294 "text:change", 1295 "text:change-start", 1296 "text:change-end", 1297 "text:reference-mark", 1298 "text:reference-mark-start", 1299 "text:reference-mark-end", 1300 ) 1301 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1302 inner = [e for e in inner if not e.xpath(request_self)] 1303 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1304 for element in inner: 1305 to_del = element.xpath(request) 1306 for elem in to_del: 1307 if isinstance(elem, Element): 1308 element.delete(elem) 1309 if no_header: # crude replace t:h by t:p 1310 new_inner = [] 1311 for element in inner: 1312 if element.tag == "text:h": 1313 children = element.children 1314 text = element.__element.text 1315 para = Element.from_tag("text:p") 1316 para.text = text or "" 1317 for c in children: 1318 para.append(c) 1319 new_inner.append(para) 1320 else: 1321 new_inner.append(element) 1322 inner = new_inner 1323 if as_text: 1324 return "\n".join([e.get_formatted_text() for e in inner]) 1325 else: 1326 return inner 1327 1328 def insert( 1329 self, 1330 element: Element, 1331 xmlposition: int | None = None, 1332 position: int | None = None, 1333 start: bool = False, 1334 ) -> None: 1335 """Insert an element relatively to ourself. 1336 1337 Insert either using DOM vocabulary or by numeric position. 1338 If text start is True, insert the element before any existing text. 1339 1340 Position start at 0. 1341 1342 Arguments: 1343 1344 element -- Element 1345 1346 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1347 or PREV_SIBLING 1348 1349 start -- Boolean 1350 1351 position -- int 1352 """ 1353 # child_tag = element.tag 1354 current = self.__element 1355 _element = element.__element 1356 if start: 1357 text = current.text 1358 if text is not None: 1359 current.text = None 1360 tail = _element.tail 1361 if tail is None: 1362 tail = text 1363 else: 1364 tail = tail + text 1365 _element.tail = tail 1366 position = 0 1367 if position is not None: 1368 current.insert(position, _element) 1369 elif xmlposition is FIRST_CHILD: 1370 current.insert(0, _element) 1371 elif xmlposition is LAST_CHILD: 1372 current.append(_element) 1373 elif xmlposition is NEXT_SIBLING: 1374 parent = current.getparent() 1375 index = parent.index(current) # type: ignore 1376 parent.insert(index + 1, _element) # type: ignore 1377 elif xmlposition is PREV_SIBLING: 1378 parent = current.getparent() 1379 index = parent.index(current) # type: ignore 1380 parent.insert(index, _element) # type: ignore 1381 else: 1382 raise ValueError("(xml)position must be defined") 1383 1384 def extend(self, odf_elements: Iterable[Element]) -> None: 1385 """Fast append elements at the end of ourself using extend.""" 1386 if odf_elements: 1387 current = self.__element 1388 elements = [element.__element for element in odf_elements] 1389 current.extend(elements) 1390 1391 def append(self, str_or_element: str | Element) -> None: 1392 """Insert element or text in the last position.""" 1393 current = self.__element 1394 if isinstance(str_or_element, str): 1395 # Has children ? 1396 children = list(current.iterchildren()) 1397 if children: 1398 # Append to tail of the last child 1399 last_child = children[-1] 1400 text = last_child.tail 1401 text = text if text is not None else "" 1402 text += str_or_element 1403 last_child.tail = text 1404 else: 1405 # Append to text of the element 1406 text = current.text 1407 text = text if text is not None else "" 1408 text += str_or_element 1409 current.text = text 1410 elif isinstance(str_or_element, Element): 1411 current.append(str_or_element.__element) 1412 else: 1413 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"') 1414 1415 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1416 """Delete the given element from the XML tree. If no element is given, 1417 "self" is deleted. The XML library may allow to continue to use an 1418 element now "orphan" as long as you have a reference to it. 1419 1420 if keep_tail is True (default), the tail text is not erased. 1421 1422 Arguments: 1423 1424 child -- Element 1425 1426 keep_tail -- boolean (default to True), True for most usages. 1427 """ 1428 if child is None: 1429 parent = self.parent 1430 if parent is None: 1431 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1432 child = self 1433 else: 1434 parent = self 1435 if keep_tail and child.__element.tail is not None: 1436 current = child.__element 1437 tail = str(current.tail) 1438 current.tail = None 1439 prev = current.getprevious() 1440 if prev is not None: 1441 if prev.tail is None: 1442 prev.tail = tail 1443 else: 1444 prev.tail += tail 1445 else: 1446 if parent.__element.text is None: 1447 parent.__element.text = tail 1448 else: 1449 parent.__element.text += tail 1450 parent.__element.remove(child.__element) 1451 1452 def replace_element(self, old_element: Element, new_element: Element) -> None: 1453 """Replaces in place a sub element with the element passed as second 1454 argument. 1455 1456 Warning : no clone for old element. 1457 """ 1458 current = self.__element 1459 current.replace(old_element.__element, new_element.__element) 1460 1461 def strip_elements( 1462 self, 1463 sub_elements: Element | Iterable[Element], 1464 ) -> Element | list: 1465 """Remove the tags of provided elements, keeping inner childs and text. 1466 1467 Return : the striped element. 1468 1469 Warning : no clone in sub_elements list. 1470 1471 Arguments: 1472 1473 sub_elements -- Element or list of Element 1474 """ 1475 if not sub_elements: 1476 return self 1477 if isinstance(sub_elements, Element): 1478 sub_elements = (sub_elements,) 1479 replacer = _get_lxml_tag("text:this-will-be-removed") 1480 for element in sub_elements: 1481 element.__element.tag = replacer 1482 strip = ("text:this-will-be-removed",) 1483 return self.strip_tags(strip=strip, default=None) 1484 1485 def strip_tags( 1486 self, 1487 strip: Iterable[str] | None = None, 1488 protect: Iterable[str] | None = None, 1489 default: str | None = "text:p", 1490 ) -> Element | list: 1491 """Remove the tags listed in strip, recursively, keeping inner childs 1492 and text. Tags listed in protect stop the removal one level depth. If 1493 the first level element is stripped, default is used to embed the 1494 content in the default element. If default is None and first level is 1495 striped, a list of text and children is returned. Return : the striped 1496 element. 1497 1498 strip_tags should be used by on purpose methods (strip_span ...) 1499 (Method name taken from lxml). 1500 1501 Arguments: 1502 1503 strip -- iterable list of str odf tags, or None 1504 1505 protect -- iterable list of str odf tags, or None 1506 1507 default -- str odf tag, or None 1508 1509 Return: 1510 1511 Element. 1512 """ 1513 if not strip: 1514 return self 1515 if not protect: 1516 protect = () 1517 protected = False 1518 element, modified = Element._strip_tags(self, strip, protect, protected) 1519 if modified and isinstance(element, list) and default: 1520 new = Element.from_tag(default) 1521 for content in element: 1522 if isinstance(content, Element): 1523 new.append(content) 1524 else: 1525 new.text = content 1526 element = new 1527 return element 1528 1529 @staticmethod 1530 def _strip_tags( # noqa:C901 1531 element: Element, 1532 strip: Iterable[str], 1533 protect: Iterable[str], 1534 protected: bool, 1535 ) -> tuple[Element | list, bool]: 1536 """Sub method for strip_tags().""" 1537 element_clone = element.clone 1538 modified = False 1539 children = [] 1540 if protect and element.tag in protect: 1541 protect_below = True 1542 else: 1543 protect_below = False 1544 for child in element_clone.children: 1545 striped_child, is_modified = Element._strip_tags( 1546 child, strip, protect, protect_below 1547 ) 1548 if is_modified: 1549 modified = True 1550 if isinstance(striped_child, list): 1551 children.extend(striped_child) 1552 else: 1553 children.append(striped_child) 1554 1555 text = element_clone.text 1556 tail = element_clone.tail 1557 if not protected and strip and element.tag in strip: 1558 element_result: list[Element | str] = [] 1559 if text is not None: 1560 element_result.append(text) 1561 for child in children: 1562 element_result.append(child) 1563 if tail is not None: 1564 element_result.append(tail) 1565 return (element_result, True) 1566 else: 1567 if not modified: 1568 return (element, False) 1569 element.clear() 1570 try: 1571 for key, value in element_clone.attributes.items(): 1572 element.set_attribute(key, value) 1573 except ValueError: 1574 sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n") 1575 if text is not None: 1576 element.append(text) 1577 for child in children: 1578 element.append(child) 1579 if tail is not None: 1580 element.tail = tail 1581 return (element, True) 1582 1583 def xpath(self, xpath_query: str) -> list[Element | Text]: 1584 """Apply XPath query to the element and its subtree. Return list of 1585 Element or Text instances translated from the nodes found. 1586 """ 1587 element = self.__element 1588 xpath_instance = xpath_compile(xpath_query) 1589 elements = xpath_instance(element) 1590 result: list[Element | Text] = [] 1591 if hasattr(elements, "__iter__"): 1592 for obj in elements: # type: ignore 1593 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1594 result.append(Text(obj)) 1595 elif isinstance(obj, _Element): 1596 result.append(Element.from_tag(obj)) 1597 # else: 1598 # result.append(obj) 1599 return result 1600 1601 def clear(self) -> None: 1602 """Remove text, children and attributes from the element.""" 1603 self.__element.clear() 1604 if hasattr(self, "_tmap"): 1605 self._tmap: list[int] = [] 1606 if hasattr(self, "_cmap"): 1607 self._cmap: list[int] = [] 1608 if hasattr(self, "_rmap"): 1609 self._rmap: list[int] = [] 1610 if hasattr(self, "_indexes"): 1611 remember = False 1612 if "_rmap" in self._indexes: 1613 remember = True 1614 self._indexes: dict[str, dict] = {} 1615 self._indexes["_cmap"] = {} 1616 self._indexes["_tmap"] = {} 1617 if remember: 1618 self._indexes["_rmap"] = {} 1619 1620 @property 1621 def clone(self) -> Element: 1622 clone = deepcopy(self.__element) 1623 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1624 root.append(clone) 1625 return self.from_tag(clone) 1626 1627 # slow data = tostring(self.__element, encoding='unicode') 1628 # return self.from_tag(data) 1629 1630 @staticmethod 1631 def _strip_namespaces(data: str) -> str: 1632 """Remove xmlns:* fields from serialized XML.""" 1633 return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data) 1634 1635 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1636 """Return text serialization of XML element.""" 1637 # This copy bypasses serialization side-effects in lxml 1638 native = deepcopy(self.__element) 1639 data = tostring( 1640 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1641 ) 1642 if with_ns: 1643 return data 1644 # Remove namespaces 1645 return self._strip_namespaces(data) 1646 1647 # Element helpers usable from any context 1648 1649 @property 1650 def document_body(self) -> Element | None: 1651 """Return the document body : 'office:body'""" 1652 return self.get_element("//office:body/*[1]") 1653 1654 @document_body.setter 1655 def document_body(self, new_body: Element) -> None: 1656 """Change in place the full document body content.""" 1657 body = self.document_body 1658 if body is None: 1659 raise ValueError("//office:body not found in document") 1660 tail = body.tail 1661 body.clear() 1662 for item in new_body.children: 1663 body.append(item) 1664 if tail: 1665 body.tail = tail 1666 1667 def get_formatted_text(self, context: dict | None = None) -> str: 1668 """This function should return a beautiful version of the text.""" 1669 return "" 1670 1671 def get_styled_elements(self, name: str = "") -> list[Element]: 1672 """Brute-force to find paragraphs, tables, etc. using the given style 1673 name (or all by default). 1674 1675 Arguments: 1676 1677 name -- str 1678 1679 Return: list 1680 """ 1681 # FIXME incomplete (and possibly inaccurate) 1682 return ( 1683 self._filtered_elements("descendant::*", text_style=name) 1684 + self._filtered_elements("descendant::*", draw_style=name) 1685 + self._filtered_elements("descendant::*", draw_text_style=name) 1686 + self._filtered_elements("descendant::*", table_style=name) 1687 + self._filtered_elements("descendant::*", page_layout=name) 1688 + self._filtered_elements("descendant::*", master_page=name) 1689 + self._filtered_elements("descendant::*", parent_style=name) 1690 ) 1691 1692 # Common attributes 1693 1694 def _get_inner_text(self, tag: str) -> str | None: 1695 element = self.get_element(tag) 1696 if element is None: 1697 return None 1698 return element.text 1699 1700 def _set_inner_text(self, tag: str, text: str) -> None: 1701 element = self.get_element(tag) 1702 if element is None: 1703 element = Element.from_tag(tag) 1704 self.append(element) 1705 element.text = text 1706 1707 # Dublin core 1708 1709 @property 1710 def dc_creator(self) -> str | None: 1711 """Get dc:creator value. 1712 1713 Return: str (or None if inexistant) 1714 """ 1715 return self._get_inner_text("dc:creator") 1716 1717 @dc_creator.setter 1718 def dc_creator(self, creator: str) -> None: 1719 """Set dc:creator value. 1720 1721 Arguments: 1722 1723 creator -- str 1724 """ 1725 self._set_inner_text("dc:creator", creator) 1726 1727 @property 1728 def dc_date(self) -> datetime | None: 1729 """Get the dc:date value. 1730 1731 Return: datetime (or None if inexistant) 1732 """ 1733 date = self._get_inner_text("dc:date") 1734 if date is None: 1735 return None 1736 return DateTime.decode(date) 1737 1738 @dc_date.setter 1739 def dc_date(self, date: datetime) -> None: 1740 """Set the dc:date value. 1741 1742 Arguments: 1743 1744 darz -- datetime 1745 """ 1746 self._set_inner_text("dc:date", DateTime.encode(date)) 1747 1748 # SVG 1749 1750 @property 1751 def svg_title(self) -> str | None: 1752 return self._get_inner_text("svg:title") 1753 1754 @svg_title.setter 1755 def svg_title(self, title: str) -> None: 1756 self._set_inner_text("svg:title", title) 1757 1758 @property 1759 def svg_description(self) -> str | None: 1760 return self._get_inner_text("svg:desc") 1761 1762 @svg_description.setter 1763 def svg_description(self, description: str) -> None: 1764 self._set_inner_text("svg:desc", description) 1765 1766 # Sections 1767 1768 def get_sections( 1769 self, 1770 style: str | None = None, 1771 content: str | None = None, 1772 ) -> list[Element]: 1773 """Return all the sections that match the criteria. 1774 1775 Arguments: 1776 1777 style -- str 1778 1779 content -- str regex 1780 1781 Return: list of Element 1782 """ 1783 return self._filtered_elements( 1784 "text:section", text_style=style, content=content 1785 ) 1786 1787 def get_section( 1788 self, 1789 position: int = 0, 1790 content: str | None = None, 1791 ) -> Element | None: 1792 """Return the section that matches the criteria. 1793 1794 Arguments: 1795 1796 position -- int 1797 1798 content -- str regex 1799 1800 Return: Element or None if not found 1801 """ 1802 return self._filtered_element( 1803 "descendant::text:section", position, content=content 1804 ) 1805 1806 # Paragraphs 1807 1808 def get_paragraphs( 1809 self, 1810 style: str | None = None, 1811 content: str | None = None, 1812 ) -> list[Element]: 1813 """Return all the paragraphs that match the criteria. 1814 1815 Arguments: 1816 1817 style -- str 1818 1819 content -- str regex 1820 1821 Return: list of Paragraph 1822 """ 1823 return self._filtered_elements( 1824 "descendant::text:p", text_style=style, content=content 1825 ) 1826 1827 def get_paragraph( 1828 self, 1829 position: int = 0, 1830 content: str | None = None, 1831 ) -> Element | None: 1832 """Return the paragraph that matches the criteria. 1833 1834 Arguments: 1835 1836 position -- int 1837 1838 content -- str regex 1839 1840 Return: Paragraph or None if not found 1841 """ 1842 return self._filtered_element("descendant::text:p", position, content=content) 1843 1844 # Span 1845 1846 def get_spans( 1847 self, 1848 style: str | None = None, 1849 content: str | None = None, 1850 ) -> list[Element]: 1851 """Return all the spans that match the criteria. 1852 1853 Arguments: 1854 1855 style -- str 1856 1857 content -- str regex 1858 1859 Return: list of Span 1860 """ 1861 return self._filtered_elements( 1862 "descendant::text:span", text_style=style, content=content 1863 ) 1864 1865 def get_span( 1866 self, 1867 position: int = 0, 1868 content: str | None = None, 1869 ) -> Element | None: 1870 """Return the span that matches the criteria. 1871 1872 Arguments: 1873 1874 position -- int 1875 1876 content -- str regex 1877 1878 Return: Span or None if not found 1879 """ 1880 return self._filtered_element( 1881 "descendant::text:span", position, content=content 1882 ) 1883 1884 # Headers 1885 1886 def get_headers( 1887 self, 1888 style: str | None = None, 1889 outline_level: str | None = None, 1890 content: str | None = None, 1891 ) -> list[Element]: 1892 """Return all the Headers that match the criteria. 1893 1894 Arguments: 1895 1896 style -- str 1897 1898 content -- str regex 1899 1900 Return: list of Header 1901 """ 1902 return self._filtered_elements( 1903 "descendant::text:h", 1904 text_style=style, 1905 outline_level=outline_level, 1906 content=content, 1907 ) 1908 1909 def get_header( 1910 self, 1911 position: int = 0, 1912 outline_level: str | None = None, 1913 content: str | None = None, 1914 ) -> Element | None: 1915 """Return the Header that matches the criteria. 1916 1917 Arguments: 1918 1919 position -- int 1920 1921 content -- str regex 1922 1923 Return: Header or None if not found 1924 """ 1925 return self._filtered_element( 1926 "descendant::text:h", 1927 position, 1928 outline_level=outline_level, 1929 content=content, 1930 ) 1931 1932 # Lists 1933 1934 def get_lists( 1935 self, 1936 style: str | None = None, 1937 content: str | None = None, 1938 ) -> list[Element]: 1939 """Return all the lists that match the criteria. 1940 1941 Arguments: 1942 1943 style -- str 1944 1945 content -- str regex 1946 1947 Return: list of List 1948 """ 1949 return self._filtered_elements( 1950 "descendant::text:list", text_style=style, content=content 1951 ) 1952 1953 def get_list( 1954 self, 1955 position: int = 0, 1956 content: str | None = None, 1957 ) -> Element | None: 1958 """Return the list that matches the criteria. 1959 1960 Arguments: 1961 1962 position -- int 1963 1964 content -- str regex 1965 1966 Return: List or None if not found 1967 """ 1968 return self._filtered_element( 1969 "descendant::text:list", position, content=content 1970 ) 1971 1972 # Frames 1973 1974 def get_frames( 1975 self, 1976 presentation_class: str | None = None, 1977 style: str | None = None, 1978 title: str | None = None, 1979 description: str | None = None, 1980 content: str | None = None, 1981 ) -> list[Element]: 1982 """Return all the frames that match the criteria. 1983 1984 Arguments: 1985 1986 presentation_class -- str 1987 1988 style -- str 1989 1990 title -- str regex 1991 1992 description -- str regex 1993 1994 content -- str regex 1995 1996 Return: list of Frame 1997 """ 1998 return self._filtered_elements( 1999 "descendant::draw:frame", 2000 presentation_class=presentation_class, 2001 draw_style=style, 2002 svg_title=title, 2003 svg_desc=description, 2004 content=content, 2005 ) 2006 2007 def get_frame( 2008 self, 2009 position: int = 0, 2010 name: str | None = None, 2011 presentation_class: str | None = None, 2012 title: str | None = None, 2013 description: str | None = None, 2014 content: str | None = None, 2015 ) -> Element | None: 2016 """Return the section that matches the criteria. 2017 2018 Arguments: 2019 2020 position -- int 2021 2022 name -- str 2023 2024 presentation_class -- str 2025 2026 title -- str regex 2027 2028 description -- str regex 2029 2030 content -- str regex 2031 2032 Return: Frame or None if not found 2033 """ 2034 return self._filtered_element( 2035 "descendant::draw:frame", 2036 position, 2037 draw_name=name, 2038 presentation_class=presentation_class, 2039 svg_title=title, 2040 svg_desc=description, 2041 content=content, 2042 ) 2043 2044 # Images 2045 2046 def get_images( 2047 self, 2048 style: str | None = None, 2049 url: str | None = None, 2050 content: str | None = None, 2051 ) -> list[Element]: 2052 """Return all the images matching the criteria. 2053 2054 Arguments: 2055 2056 style -- str 2057 2058 url -- str regex 2059 2060 content -- str regex 2061 2062 Return: list of Element 2063 """ 2064 return self._filtered_elements( 2065 "descendant::draw:image", text_style=style, url=url, content=content 2066 ) 2067 2068 def get_image( 2069 self, 2070 position: int = 0, 2071 name: str | None = None, 2072 url: str | None = None, 2073 content: str | None = None, 2074 ) -> Element | None: 2075 """Return the image matching the criteria. 2076 2077 Arguments: 2078 2079 position -- int 2080 2081 name -- str 2082 2083 url -- str regex 2084 2085 content -- str regex 2086 2087 Return: Element or None if not found 2088 """ 2089 # The frame is holding the name 2090 if name is not None: 2091 frame = self._filtered_element( 2092 "descendant::draw:frame", position, draw_name=name 2093 ) 2094 if frame is None: 2095 return None 2096 # The name is supposedly unique 2097 return frame.get_element("draw:image") 2098 return self._filtered_element( 2099 "descendant::draw:image", position, url=url, content=content 2100 ) 2101 2102 # Tables 2103 2104 def get_tables( 2105 self, 2106 style: str | None = None, 2107 content: str | None = None, 2108 ) -> list[Element]: 2109 """Return all the tables that match the criteria. 2110 2111 Arguments: 2112 2113 style -- str 2114 2115 content -- str regex 2116 2117 Return: list of Table 2118 """ 2119 return self._filtered_elements( 2120 "descendant::table:table", table_style=style, content=content 2121 ) 2122 2123 def get_table( 2124 self, 2125 position: int = 0, 2126 name: str | None = None, 2127 content: str | None = None, 2128 ) -> Element | None: 2129 """Return the table that matches the criteria. 2130 2131 Arguments: 2132 2133 position -- int 2134 2135 name -- str 2136 2137 content -- str regex 2138 2139 Return: Table or None if not found 2140 """ 2141 if name is None and content is None: 2142 result = self._filtered_element("descendant::table:table", position) 2143 else: 2144 result = self._filtered_element( 2145 "descendant::table:table", 2146 position, 2147 table_name=name, 2148 content=content, 2149 ) 2150 return result 2151 2152 # Named Range 2153 2154 def get_named_ranges(self) -> list[Element]: 2155 """Return all the tables named ranges. 2156 2157 Return: list of odf_named_range 2158 """ 2159 named_ranges = self.get_elements( 2160 "descendant::table:named-expressions/table:named-range" 2161 ) 2162 return named_ranges 2163 2164 def get_named_range(self, name: str) -> Element | None: 2165 """Return the named range of specified name, or None if not found. 2166 2167 Arguments: 2168 2169 name -- str 2170 2171 Return: NamedRange 2172 """ 2173 named_range = self.get_elements( 2174 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2175 ) 2176 if named_range: 2177 return named_range[0] 2178 else: 2179 return None 2180 2181 def append_named_range(self, named_range: Element) -> None: 2182 """Append the named range to the spreadsheet, replacing existing named 2183 range of same name if any. 2184 2185 Arguments: 2186 2187 named_range -- NamedRange 2188 """ 2189 if self.tag != "office:spreadsheet": 2190 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2191 named_expressions = self.get_element("table:named-expressions") 2192 if not named_expressions: 2193 named_expressions = Element.from_tag("table:named-expressions") 2194 self.append(named_expressions) 2195 # exists ? 2196 current = named_expressions.get_element( 2197 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2198 ) 2199 if current: 2200 named_expressions.delete(current) 2201 named_expressions.append(named_range) 2202 2203 def delete_named_range(self, name: str) -> None: 2204 """Delete the Named Range of specified name from the spreadsheet. 2205 2206 Arguments: 2207 2208 name -- str 2209 """ 2210 if self.tag != "office:spreadsheet": 2211 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2212 named_range = self.get_named_range(name) 2213 if not named_range: 2214 return 2215 named_range.delete() 2216 named_expressions = self.get_element("table:named-expressions") 2217 if not named_expressions: 2218 return 2219 element = named_expressions.__element 2220 children = list(element.iterchildren()) 2221 if not children: 2222 self.delete(named_expressions) 2223 2224 # Notes 2225 2226 def get_notes( 2227 self, 2228 note_class: str | None = None, 2229 content: str | None = None, 2230 ) -> list[Element]: 2231 """Return all the notes that match the criteria. 2232 2233 Arguments: 2234 2235 note_class -- 'footnote' or 'endnote' 2236 2237 content -- str regex 2238 2239 Return: list of Note 2240 """ 2241 return self._filtered_elements( 2242 "descendant::text:note", note_class=note_class, content=content 2243 ) 2244 2245 def get_note( 2246 self, 2247 position: int = 0, 2248 note_id: str | None = None, 2249 note_class: str | None = None, 2250 content: str | None = None, 2251 ) -> Element | None: 2252 """Return the note that matches the criteria. 2253 2254 Arguments: 2255 2256 position -- int 2257 2258 note_id -- str 2259 2260 note_class -- 'footnote' or 'endnote' 2261 2262 content -- str regex 2263 2264 Return: Note or None if not found 2265 """ 2266 return self._filtered_element( 2267 "descendant::text:note", 2268 position, 2269 text_id=note_id, 2270 note_class=note_class, 2271 content=content, 2272 ) 2273 2274 # Annotations 2275 2276 def get_annotations( 2277 self, 2278 creator: str | None = None, 2279 start_date: datetime | None = None, 2280 end_date: datetime | None = None, 2281 content: str | None = None, 2282 ) -> list[Element]: 2283 """Return all the annotations that match the criteria. 2284 2285 Arguments: 2286 2287 creator -- str 2288 2289 start_date -- datetime instance 2290 2291 end_date -- datetime instance 2292 2293 content -- str regex 2294 2295 Return: list of Annotation 2296 """ 2297 annotations = [] 2298 for annotation in self._filtered_elements( 2299 "descendant::office:annotation", content=content 2300 ): 2301 if creator is not None and creator != annotation.dc_creator: 2302 continue 2303 date = annotation.dc_date 2304 if date is None: 2305 continue 2306 if start_date is not None and date < start_date: 2307 continue 2308 if end_date is not None and date >= end_date: 2309 continue 2310 annotations.append(annotation) 2311 return annotations 2312 2313 def get_annotation( 2314 self, 2315 position: int = 0, 2316 creator: str | None = None, 2317 start_date: datetime | None = None, 2318 end_date: datetime | None = None, 2319 content: str | None = None, 2320 name: str | None = None, 2321 ) -> Element | None: 2322 """Return the annotation that matches the criteria. 2323 2324 Arguments: 2325 2326 position -- int 2327 2328 creator -- str 2329 2330 start_date -- datetime instance 2331 2332 end_date -- datetime instance 2333 2334 content -- str regex 2335 2336 name -- str 2337 2338 Return: Annotation or None if not found 2339 """ 2340 if name is not None: 2341 return self._filtered_element( 2342 "descendant::office:annotation", 0, office_name=name 2343 ) 2344 annotations = self.get_annotations( 2345 creator=creator, start_date=start_date, end_date=end_date, content=content 2346 ) 2347 if not annotations: 2348 return None 2349 try: 2350 return annotations[position] 2351 except IndexError: 2352 return None 2353 2354 def get_annotation_ends(self) -> list[Element]: 2355 """Return all the annotation ends. 2356 2357 Return: list of Element 2358 """ 2359 return self._filtered_elements("descendant::office:annotation-end") 2360 2361 def get_annotation_end( 2362 self, 2363 position: int = 0, 2364 name: str | None = None, 2365 ) -> Element | None: 2366 """Return the annotation end that matches the criteria. 2367 2368 Arguments: 2369 2370 position -- int 2371 2372 name -- str 2373 2374 Return: Element or None if not found 2375 """ 2376 return self._filtered_element( 2377 "descendant::office:annotation-end", position, office_name=name 2378 ) 2379 2380 # office:names 2381 2382 def get_office_names(self) -> list[str]: 2383 """Return all the used office:name tags values of the element. 2384 2385 Return: list of unique str 2386 """ 2387 name_xpath_query = xpath_compile("//@office:name") 2388 response = name_xpath_query(self.__element) 2389 if not isinstance(response, list): 2390 return [] 2391 return list({str(name) for name in response if name}) 2392 2393 # Variables 2394 2395 def get_variable_decls(self) -> Element: 2396 """Return the container for variable declarations. Created if not 2397 found. 2398 2399 Return: Element 2400 """ 2401 variable_decls = self.get_element("//text:variable-decls") 2402 if variable_decls is None: 2403 body = self.document_body 2404 if not body: 2405 raise ValueError("Empty document.body") 2406 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2407 variable_decls = body.get_element("//text:variable-decls") 2408 2409 return variable_decls # type:ignore 2410 2411 def get_variable_decl_list(self) -> list[Element]: 2412 """Return all the variable declarations. 2413 2414 Return: list of Element 2415 """ 2416 return self._filtered_elements("descendant::text:variable-decl") 2417 2418 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2419 """return the variable declaration for the given name. 2420 2421 Arguments: 2422 2423 name -- str 2424 2425 position -- int 2426 2427 return: Element or none if not found 2428 """ 2429 return self._filtered_element( 2430 "descendant::text:variable-decl", position, text_name=name 2431 ) 2432 2433 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2434 """Return all the variable sets that match the criteria. 2435 2436 Arguments: 2437 2438 name -- str 2439 2440 Return: list of Element 2441 """ 2442 return self._filtered_elements("descendant::text:variable-set", text_name=name) 2443 2444 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2445 """Return the variable set for the given name (last one by default). 2446 2447 Arguments: 2448 2449 name -- str 2450 2451 position -- int 2452 2453 Return: Element or None if not found 2454 """ 2455 return self._filtered_element( 2456 "descendant::text:variable-set", position, text_name=name 2457 ) 2458 2459 def get_variable_set_value( 2460 self, 2461 name: str, 2462 value_type: str | None = None, 2463 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2464 """Return the last value of the given variable name. 2465 2466 Arguments: 2467 2468 name -- str 2469 2470 value_type -- 'boolean', 'currency', 'date', 'float', 2471 'percentage', 'string', 'time' or automatic 2472 2473 Return: most appropriate Python type 2474 """ 2475 variable_set = self.get_variable_set(name) 2476 if not variable_set: 2477 return None 2478 return variable_set.get_value(value_type) # type: ignore 2479 2480 # User fields 2481 2482 def get_user_field_decls(self) -> Element | None: 2483 """Return the container for user field declarations. Created if not 2484 found. 2485 2486 Return: Element 2487 """ 2488 user_field_decls = self.get_element("//text:user-field-decls") 2489 if user_field_decls is None: 2490 body = self.document_body 2491 if not body: 2492 raise ValueError("Empty document.body") 2493 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2494 user_field_decls = body.get_element("//text:user-field-decls") 2495 2496 return user_field_decls 2497 2498 def get_user_field_decl_list(self) -> list[Element]: 2499 """Return all the user field declarations. 2500 2501 Return: list of Element 2502 """ 2503 return self._filtered_elements("descendant::text:user-field-decl") 2504 2505 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2506 """return the user field declaration for the given name. 2507 2508 return: Element or none if not found 2509 """ 2510 return self._filtered_element( 2511 "descendant::text:user-field-decl", position, text_name=name 2512 ) 2513 2514 def get_user_field_value( 2515 self, name: str, value_type: str | None = None 2516 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2517 """Return the value of the given user field name. 2518 2519 Arguments: 2520 2521 name -- str 2522 2523 value_type -- 'boolean', 'currency', 'date', 'float', 2524 'percentage', 'string', 'time' or automatic 2525 2526 Return: most appropriate Python type 2527 """ 2528 user_field_decl = self.get_user_field_decl(name) 2529 if user_field_decl is None: 2530 return None 2531 return user_field_decl.get_value(value_type) # type: ignore 2532 2533 # User defined fields 2534 # They are fields who should contain a copy of a user defined medtadata 2535 2536 def get_user_defined_list(self) -> list[Element]: 2537 """Return all the user defined field declarations. 2538 2539 Return: list of Element 2540 """ 2541 return self._filtered_elements("descendant::text:user-defined") 2542 2543 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2544 """return the user defined declaration for the given name. 2545 2546 return: Element or none if not found 2547 """ 2548 return self._filtered_element( 2549 "descendant::text:user-defined", position, text_name=name 2550 ) 2551 2552 def get_user_defined_value( 2553 self, name: str, value_type: str | None = None 2554 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2555 """Return the value of the given user defined field name. 2556 2557 Arguments: 2558 2559 name -- str 2560 2561 value_type -- 'boolean', 'date', 'float', 2562 'string', 'time' or automatic 2563 2564 Return: most appropriate Python type 2565 """ 2566 user_defined = self.get_user_defined(name) 2567 if user_defined is None: 2568 return None 2569 return user_defined.get_value(value_type) # type: ignore 2570 2571 # Draw Pages 2572 2573 def get_draw_pages( 2574 self, 2575 style: str | None = None, 2576 content: str | None = None, 2577 ) -> list[Element]: 2578 """Return all the draw pages that match the criteria. 2579 2580 Arguments: 2581 2582 style -- str 2583 2584 content -- str regex 2585 2586 Return: list of DrawPage 2587 """ 2588 return self._filtered_elements( 2589 "descendant::draw:page", draw_style=style, content=content 2590 ) 2591 2592 def get_draw_page( 2593 self, 2594 position: int = 0, 2595 name: str | None = None, 2596 content: str | None = None, 2597 ) -> Element | None: 2598 """Return the draw page that matches the criteria. 2599 2600 Arguments: 2601 2602 position -- int 2603 2604 name -- str 2605 2606 content -- str regex 2607 2608 Return: DrawPage or None if not found 2609 """ 2610 return self._filtered_element( 2611 "descendant::draw:page", position, draw_name=name, content=content 2612 ) 2613 2614 # Links 2615 2616 def get_links( 2617 self, 2618 name: str | None = None, 2619 title: str | None = None, 2620 url: str | None = None, 2621 content: str | None = None, 2622 ) -> list[Element]: 2623 """Return all the links that match the criteria. 2624 2625 Arguments: 2626 2627 name -- str 2628 2629 title -- str 2630 2631 url -- str regex 2632 2633 content -- str regex 2634 2635 Return: list of Element 2636 """ 2637 return self._filtered_elements( 2638 "descendant::text:a", 2639 office_name=name, 2640 office_title=title, 2641 url=url, 2642 content=content, 2643 ) 2644 2645 def get_link( 2646 self, 2647 position: int = 0, 2648 name: str | None = None, 2649 title: str | None = None, 2650 url: str | None = None, 2651 content: str | None = None, 2652 ) -> Element | None: 2653 """Return the link that matches the criteria. 2654 2655 Arguments: 2656 2657 position -- int 2658 2659 name -- str 2660 2661 title -- str 2662 2663 url -- str regex 2664 2665 content -- str regex 2666 2667 Return: Element or None if not found 2668 """ 2669 return self._filtered_element( 2670 "descendant::text:a", 2671 position, 2672 office_name=name, 2673 office_title=title, 2674 url=url, 2675 content=content, 2676 ) 2677 2678 # Bookmarks 2679 2680 def get_bookmarks(self) -> list[Element]: 2681 """Return all the bookmarks. 2682 2683 Return: list of Element 2684 """ 2685 return self._filtered_elements("descendant::text:bookmark") 2686 2687 def get_bookmark( 2688 self, 2689 position: int = 0, 2690 name: str | None = None, 2691 ) -> Element | None: 2692 """Return the bookmark that matches the criteria. 2693 2694 Arguments: 2695 2696 position -- int 2697 2698 name -- str 2699 2700 Return: Bookmark or None if not found 2701 """ 2702 return self._filtered_element( 2703 "descendant::text:bookmark", position, text_name=name 2704 ) 2705 2706 def get_bookmark_starts(self) -> list[Element]: 2707 """Return all the bookmark starts. 2708 2709 Return: list of Element 2710 """ 2711 return self._filtered_elements("descendant::text:bookmark-start") 2712 2713 def get_bookmark_start( 2714 self, 2715 position: int = 0, 2716 name: str | None = None, 2717 ) -> Element | None: 2718 """Return the bookmark start that matches the criteria. 2719 2720 Arguments: 2721 2722 position -- int 2723 2724 name -- str 2725 2726 Return: Element or None if not found 2727 """ 2728 return self._filtered_element( 2729 "descendant::text:bookmark-start", position, text_name=name 2730 ) 2731 2732 def get_bookmark_ends(self) -> list[Element]: 2733 """Return all the bookmark ends. 2734 2735 Return: list of Element 2736 """ 2737 return self._filtered_elements("descendant::text:bookmark-end") 2738 2739 def get_bookmark_end( 2740 self, 2741 position: int = 0, 2742 name: str | None = None, 2743 ) -> Element | None: 2744 """Return the bookmark end that matches the criteria. 2745 2746 Arguments: 2747 2748 position -- int 2749 2750 name -- str 2751 2752 Return: Element or None if not found 2753 """ 2754 return self._filtered_element( 2755 "descendant::text:bookmark-end", position, text_name=name 2756 ) 2757 2758 # Reference marks 2759 2760 def get_reference_marks_single(self) -> list[Element]: 2761 """Return all the reference marks. Search only the tags 2762 text:reference-mark. 2763 Consider using : get_reference_marks() 2764 2765 Return: list of Element 2766 """ 2767 return self._filtered_elements("descendant::text:reference-mark") 2768 2769 def get_reference_mark_single( 2770 self, 2771 position: int = 0, 2772 name: str | None = None, 2773 ) -> Element | None: 2774 """Return the reference mark that matches the criteria. Search only the 2775 tags text:reference-mark. 2776 Consider using : get_reference_mark() 2777 2778 Arguments: 2779 2780 position -- int 2781 2782 name -- str 2783 2784 Return: Element or None if not found 2785 """ 2786 return self._filtered_element( 2787 "descendant::text:reference-mark", position, text_name=name 2788 ) 2789 2790 def get_reference_mark_starts(self) -> list[Element]: 2791 """Return all the reference mark starts. Search only the tags 2792 text:reference-mark-start. 2793 Consider using : get_reference_marks() 2794 2795 Return: list of Element 2796 """ 2797 return self._filtered_elements("descendant::text:reference-mark-start") 2798 2799 def get_reference_mark_start( 2800 self, 2801 position: int = 0, 2802 name: str | None = None, 2803 ) -> Element | None: 2804 """Return the reference mark start that matches the criteria. Search 2805 only the tags text:reference-mark-start. 2806 Consider using : get_reference_mark() 2807 2808 Arguments: 2809 2810 position -- int 2811 2812 name -- str 2813 2814 Return: Element or None if not found 2815 """ 2816 return self._filtered_element( 2817 "descendant::text:reference-mark-start", position, text_name=name 2818 ) 2819 2820 def get_reference_mark_ends(self) -> list[Element]: 2821 """Return all the reference mark ends. Search only the tags 2822 text:reference-mark-end. 2823 Consider using : get_reference_marks() 2824 2825 Return: list of Element 2826 """ 2827 return self._filtered_elements("descendant::text:reference-mark-end") 2828 2829 def get_reference_mark_end( 2830 self, 2831 position: int = 0, 2832 name: str | None = None, 2833 ) -> Element | None: 2834 """Return the reference mark end that matches the criteria. Search only 2835 the tags text:reference-mark-end. 2836 Consider using : get_reference_marks() 2837 2838 Arguments: 2839 2840 position -- int 2841 2842 name -- str 2843 2844 Return: Element or None if not found 2845 """ 2846 return self._filtered_element( 2847 "descendant::text:reference-mark-end", position, text_name=name 2848 ) 2849 2850 def get_reference_marks(self) -> list[Element]: 2851 """Return all the reference marks, either single position reference 2852 (text:reference-mark) or start of range reference 2853 (text:reference-mark-start). 2854 2855 Return: list of Element 2856 """ 2857 return self._filtered_elements( 2858 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2859 ) 2860 2861 def get_reference_mark( 2862 self, 2863 position: int = 0, 2864 name: str | None = None, 2865 ) -> Element | None: 2866 """Return the reference mark that match the criteria. Either single 2867 position reference mark (text:reference-mark) or start of range 2868 reference (text:reference-mark-start). 2869 2870 Arguments: 2871 2872 position -- int 2873 2874 name -- str 2875 2876 Return: Element or None if not found 2877 """ 2878 if name: 2879 request = ( 2880 f"descendant::text:reference-mark-start" 2881 f'[@text:name="{name}"] ' 2882 f"| descendant::text:reference-mark" 2883 f'[@text:name="{name}"]' 2884 ) 2885 return self._filtered_element(request, position=0) 2886 request = ( 2887 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2888 ) 2889 return self._filtered_element(request, position) 2890 2891 def get_references(self, name: str | None = None) -> list[Element]: 2892 """Return all the references (text:reference-ref). If name is 2893 provided, returns the references of that name. 2894 2895 Return: list of Element 2896 2897 Arguments: 2898 2899 name -- str or None 2900 """ 2901 if name is None: 2902 return self._filtered_elements("descendant::text:reference-ref") 2903 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2904 return self._filtered_elements(request) 2905 2906 # Shapes elements 2907 2908 # Groups 2909 2910 def get_draw_groups( 2911 self, 2912 title: str | None = None, 2913 description: str | None = None, 2914 content: str | None = None, 2915 ) -> list[Element]: 2916 return self._filtered_elements( 2917 "descendant::draw:g", 2918 svg_title=title, 2919 svg_desc=description, 2920 content=content, 2921 ) 2922 2923 def get_draw_group( 2924 self, 2925 position: int = 0, 2926 name: str | None = None, 2927 title: str | None = None, 2928 description: str | None = None, 2929 content: str | None = None, 2930 ) -> Element | None: 2931 return self._filtered_element( 2932 "descendant::draw:g", 2933 position, 2934 draw_name=name, 2935 svg_title=title, 2936 svg_desc=description, 2937 content=content, 2938 ) 2939 2940 # Lines 2941 2942 def get_draw_lines( 2943 self, 2944 draw_style: str | None = None, 2945 draw_text_style: str | None = None, 2946 content: str | None = None, 2947 ) -> list[Element]: 2948 """Return all the draw lines that match the criteria. 2949 2950 Arguments: 2951 2952 draw_style -- str 2953 2954 draw_text_style -- str 2955 2956 content -- str regex 2957 2958 Return: list of odf_shape 2959 """ 2960 return self._filtered_elements( 2961 "descendant::draw:line", 2962 draw_style=draw_style, 2963 draw_text_style=draw_text_style, 2964 content=content, 2965 ) 2966 2967 def get_draw_line( 2968 self, 2969 position: int = 0, 2970 id: str | None = None, # noqa:A002 2971 content: str | None = None, 2972 ) -> Element | None: 2973 """Return the draw line that matches the criteria. 2974 2975 Arguments: 2976 2977 position -- int 2978 2979 id -- str 2980 2981 content -- str regex 2982 2983 Return: odf_shape or None if not found 2984 """ 2985 return self._filtered_element( 2986 "descendant::draw:line", position, draw_id=id, content=content 2987 ) 2988 2989 # Rectangles 2990 2991 def get_draw_rectangles( 2992 self, 2993 draw_style: str | None = None, 2994 draw_text_style: str | None = None, 2995 content: str | None = None, 2996 ) -> list[Element]: 2997 """Return all the draw rectangles that match the criteria. 2998 2999 Arguments: 3000 3001 draw_style -- str 3002 3003 draw_text_style -- str 3004 3005 content -- str regex 3006 3007 Return: list of odf_shape 3008 """ 3009 return self._filtered_elements( 3010 "descendant::draw:rect", 3011 draw_style=draw_style, 3012 draw_text_style=draw_text_style, 3013 content=content, 3014 ) 3015 3016 def get_draw_rectangle( 3017 self, 3018 position: int = 0, 3019 id: str | None = None, # noqa:A002 3020 content: str | None = None, 3021 ) -> Element | None: 3022 """Return the draw rectangle that matches the criteria. 3023 3024 Arguments: 3025 3026 position -- int 3027 3028 id -- str 3029 3030 content -- str regex 3031 3032 Return: odf_shape or None if not found 3033 """ 3034 return self._filtered_element( 3035 "descendant::draw:rect", position, draw_id=id, content=content 3036 ) 3037 3038 # Ellipse 3039 3040 def get_draw_ellipses( 3041 self, 3042 draw_style: str | None = None, 3043 draw_text_style: str | None = None, 3044 content: str | None = None, 3045 ) -> list[Element]: 3046 """Return all the draw ellipses that match the criteria. 3047 3048 Arguments: 3049 3050 draw_style -- str 3051 3052 draw_text_style -- str 3053 3054 content -- str regex 3055 3056 Return: list of odf_shape 3057 """ 3058 return self._filtered_elements( 3059 "descendant::draw:ellipse", 3060 draw_style=draw_style, 3061 draw_text_style=draw_text_style, 3062 content=content, 3063 ) 3064 3065 def get_draw_ellipse( 3066 self, 3067 position: int = 0, 3068 id: str | None = None, # noqa:A002 3069 content: str | None = None, 3070 ) -> Element | None: 3071 """Return the draw ellipse that matches the criteria. 3072 3073 Arguments: 3074 3075 position -- int 3076 3077 id -- str 3078 3079 content -- str regex 3080 3081 Return: odf_shape or None if not found 3082 """ 3083 return self._filtered_element( 3084 "descendant::draw:ellipse", position, draw_id=id, content=content 3085 ) 3086 3087 # Connectors 3088 3089 def get_draw_connectors( 3090 self, 3091 draw_style: str | None = None, 3092 draw_text_style: str | None = None, 3093 content: str | None = None, 3094 ) -> list[Element]: 3095 """Return all the draw connectors that match the criteria. 3096 3097 Arguments: 3098 3099 draw_style -- str 3100 3101 draw_text_style -- str 3102 3103 content -- str regex 3104 3105 Return: list of odf_shape 3106 """ 3107 return self._filtered_elements( 3108 "descendant::draw:connector", 3109 draw_style=draw_style, 3110 draw_text_style=draw_text_style, 3111 content=content, 3112 ) 3113 3114 def get_draw_connector( 3115 self, 3116 position: int = 0, 3117 id: str | None = None, # noqa:A002 3118 content: str | None = None, 3119 ) -> Element | None: 3120 """Return the draw connector that matches the criteria. 3121 3122 Arguments: 3123 3124 position -- int 3125 3126 id -- str 3127 3128 content -- str regex 3129 3130 Return: odf_shape or None if not found 3131 """ 3132 return self._filtered_element( 3133 "descendant::draw:connector", position, draw_id=id, content=content 3134 ) 3135 3136 def get_orphan_draw_connectors(self) -> list[Element]: 3137 """Return a list of connectors which don't have any shape connected 3138 to them. 3139 """ 3140 connectors = [] 3141 for connector in self.get_draw_connectors(): 3142 start_shape = connector.get_attribute("draw:start-shape") 3143 end_shape = connector.get_attribute("draw:end-shape") 3144 if start_shape is None and end_shape is None: 3145 connectors.append(connector) 3146 return connectors 3147 3148 # Tracked changes and text change 3149 3150 def get_tracked_changes(self) -> Element | None: 3151 """Return the tracked-changes part in the text body.""" 3152 return self.get_element("//text:tracked-changes") 3153 3154 def get_changes_ids(self) -> list[Element | Text]: 3155 """Return a list of ids that refers to a change region in the tracked 3156 changes list. 3157 """ 3158 # Insertion changes 3159 xpath_query = "descendant::text:change-start/@text:change-id" 3160 # Deletion changes 3161 xpath_query += " | descendant::text:change/@text:change-id" 3162 return self.xpath(xpath_query) 3163 3164 def get_text_change_deletions(self) -> list[Element]: 3165 """Return all the text changes of deletion kind: the tags text:change. 3166 Consider using : get_text_changes() 3167 3168 Return: list of Element 3169 """ 3170 return self._filtered_elements("descendant::text:text:change") 3171 3172 def get_text_change_deletion( 3173 self, 3174 position: int = 0, 3175 idx: str | None = None, 3176 ) -> Element | None: 3177 """Return the text change of deletion kind that matches the criteria. 3178 Search only for the tags text:change. 3179 Consider using : get_text_change() 3180 3181 Arguments: 3182 3183 position -- int 3184 3185 idx -- str 3186 3187 Return: Element or None if not found 3188 """ 3189 return self._filtered_element( 3190 "descendant::text:change", position, change_id=idx 3191 ) 3192 3193 def get_text_change_starts(self) -> list[Element]: 3194 """Return all the text change-start. Search only for the tags 3195 text:change-start. 3196 Consider using : get_text_changes() 3197 3198 Return: list of Element 3199 """ 3200 return self._filtered_elements("descendant::text:change-start") 3201 3202 def get_text_change_start( 3203 self, 3204 position: int = 0, 3205 idx: str | None = None, 3206 ) -> Element | None: 3207 """Return the text change-start that matches the criteria. Search 3208 only the tags text:change-start. 3209 Consider using : get_text_change() 3210 3211 Arguments: 3212 3213 position -- int 3214 3215 idx -- str 3216 3217 Return: Element or None if not found 3218 """ 3219 return self._filtered_element( 3220 "descendant::text:change-start", position, change_id=idx 3221 ) 3222 3223 def get_text_change_ends(self) -> list[Element]: 3224 """Return all the text change-end. Search only the tags 3225 text:change-end. 3226 Consider using : get_text_changes() 3227 3228 Return: list of Element 3229 """ 3230 return self._filtered_elements("descendant::text:change-end") 3231 3232 def get_text_change_end( 3233 self, 3234 position: int = 0, 3235 idx: str | None = None, 3236 ) -> Element | None: 3237 """Return the text change-end that matches the criteria. Search only 3238 the tags text:change-end. 3239 Consider using : get_text_change() 3240 3241 Arguments: 3242 3243 position -- int 3244 3245 idx -- str 3246 3247 Return: Element or None if not found 3248 """ 3249 return self._filtered_element( 3250 "descendant::text:change-end", position, change_id=idx 3251 ) 3252 3253 def get_text_changes(self) -> list[Element]: 3254 """Return all the text changes, either single deletion 3255 (text:change) or start of range of changes (text:change-start). 3256 3257 Return: list of Element 3258 """ 3259 request = "descendant::text:change-start | descendant::text:change" 3260 return self._filtered_elements(request) 3261 3262 def get_text_change( 3263 self, 3264 position: int = 0, 3265 idx: str | None = None, 3266 ) -> Element | None: 3267 """Return the text change that matches the criteria. Either single 3268 deletion (text:change) or start of range of changes (text:change-start). 3269 position : index of the element to retrieve if several matches, default 3270 is 0. 3271 idx : change-id of the element. 3272 3273 Arguments: 3274 3275 position -- int 3276 3277 idx -- str 3278 3279 Return: Element or None if not found 3280 """ 3281 if idx: 3282 request = ( 3283 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3284 f'| descendant::text:change[@text:change-id="{idx}"]' 3285 ) 3286 return self._filtered_element(request, 0) 3287 request = "descendant::text:change-start | descendant::text:change" 3288 return self._filtered_element(request, position) 3289 3290 # Table Of Content 3291 3292 def get_tocs(self) -> list[Element]: 3293 """Return all the tables of contents. 3294 3295 Return: list of odf_toc 3296 """ 3297 return self._filtered_elements("text:table-of-content") 3298 3299 def get_toc( 3300 self, 3301 position: int = 0, 3302 content: str | None = None, 3303 ) -> Element | None: 3304 """Return the table of contents that matches the criteria. 3305 3306 Arguments: 3307 3308 position -- int 3309 3310 content -- str regex 3311 3312 Return: odf_toc or None if not found 3313 """ 3314 return self._filtered_element( 3315 "text:table-of-content", position, content=content 3316 ) 3317 3318 # Styles 3319 3320 @staticmethod 3321 def _get_style_tagname(family: str | None, is_default: bool = False) -> str: 3322 """Widely match possible tag names given the family (or not).""" 3323 if not family: 3324 tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)" 3325 elif is_default: 3326 # Default style 3327 tagname = "style:default-style" 3328 else: 3329 tagname = _family_style_tagname(family) 3330 # if famattr: 3331 # # Include family default style 3332 # tagname = '(%s|style:default-style)' % tagname 3333 if family in FAMILY_ODF_STD: 3334 # Include family default style 3335 tagname = f"({tagname}|style:default-style)" 3336 return tagname 3337 3338 def get_styles(self, family: str | None = None) -> list[Element]: 3339 # Both common and default styles 3340 tagname = self._get_style_tagname(family) 3341 return self._filtered_elements(tagname, family=family) 3342 3343 def get_style( 3344 self, 3345 family: str, 3346 name_or_element: str | Element | None = None, 3347 display_name: str | None = None, 3348 ) -> Element | None: 3349 """Return the style uniquely identified by the family/name pair. If 3350 the argument is already a style object, it will return it. 3351 3352 If the name is not the internal name but the name you gave in the 3353 desktop application, use display_name instead. 3354 3355 Arguments: 3356 3357 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3358 'number' 3359 3360 name_or_element -- str or Style 3361 3362 display_name -- str 3363 3364 Return: odf_style or None if not found 3365 """ 3366 if isinstance(name_or_element, Element): 3367 name = self.get_attribute("style:name") 3368 if name is not None: 3369 return name_or_element 3370 else: 3371 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3372 style_name = name_or_element 3373 is_default = not (style_name or display_name) 3374 tagname = self._get_style_tagname(family, is_default=is_default) 3375 # famattr became None if no "style:family" attribute 3376 if family: 3377 return self._filtered_element( 3378 tagname, 3379 0, 3380 style_name=style_name, 3381 display_name=display_name, 3382 family=family, 3383 ) 3384 else: 3385 return self._filtered_element( 3386 tagname, 3387 0, 3388 draw_name=style_name or display_name, 3389 family=family, 3390 ) 3391 3392 def _filtered_element( 3393 self, 3394 query_string: str, 3395 position: int, 3396 **kwargs: Any, 3397 ) -> Element | None: 3398 results = self._filtered_elements(query_string, **kwargs) 3399 try: 3400 return results[position] 3401 except IndexError: 3402 return None 3403 3404 def _filtered_elements( 3405 self, 3406 query_string: str, 3407 content: str | None = None, 3408 url: str | None = None, 3409 svg_title: str | None = None, 3410 svg_desc: str | None = None, 3411 dc_creator: str | None = None, 3412 dc_date: datetime | None = None, 3413 **kwargs: Any, 3414 ) -> list[Element]: 3415 query = make_xpath_query(query_string, **kwargs) 3416 elements = self.get_elements(query) 3417 # Filter the elements with the regex (TODO use XPath) 3418 if content is not None: 3419 elements = [element for element in elements if element.match(content)] 3420 if url is not None: 3421 filtered = [] 3422 for element in elements: 3423 url_attr = element.get_attribute("xlink:href") 3424 if isinstance(url_attr, str) and search(url, url_attr) is not None: 3425 filtered.append(element) 3426 elements = filtered 3427 if dc_date is None: 3428 dt_dc_date = None 3429 else: 3430 dt_dc_date = DateTime.encode(dc_date) 3431 for variable, childname in [ 3432 (svg_title, "svg:title"), 3433 (svg_desc, "svg:desc"), 3434 (dc_creator, "descendant::dc:creator"), 3435 (dt_dc_date, "descendant::dc:date"), 3436 ]: 3437 if not variable: 3438 continue 3439 filtered = [] 3440 for element in elements: 3441 child = element.get_element(childname) 3442 if child and child.match(variable): 3443 filtered.append(element) 3444 elements = filtered 3445 return elements
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
316 def __init__(self, **kwargs: Any) -> None: 317 tag_or_elem = kwargs.pop("tag_or_elem", None) 318 if tag_or_elem is None: 319 # Instance for newly created object: create new lxml element and 320 # continue by subclass __init__ 321 # If the tag key word exists, make a custom element 322 self._do_init = True 323 tag = kwargs.pop("tag", self._tag) 324 self.__element = self.make_etree_element(tag) 325 else: 326 # called with an existing lxml element, sould be a result of 327 # from_tag() casting, do not execute the subclass __init__ 328 if not isinstance(tag_or_elem, _Element): 329 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 330 self._do_init = False 331 self.__element = tag_or_elem
339 @classmethod 340 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 341 """Element class and subclass factory. 342 343 Turn an lxml Element or ODF string tag into an ODF XML Element 344 of the relevant class. 345 346 Arguments: 347 348 tag_or_elem -- ODF str tag or lxml.Element 349 350 Return: Element (or subclass) instance 351 """ 352 if isinstance(tag_or_elem, str): 353 # assume the argument is a prefix:name tag 354 elem = cls.make_etree_element(tag_or_elem) 355 else: 356 elem = tag_or_elem 357 klass = _class_registry.get(elem.tag, cls) 358 return klass(tag_or_elem=elem)
Element class and subclass factory.
Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.
Arguments:
tag_or_elem -- ODF str tag or lxml.Element
Return: Element (or subclass) instance
360 @classmethod 361 def from_tag_for_clone( 362 cls: type, 363 tree_element: _Element, 364 cache: tuple | None, 365 ) -> Element: 366 tag = to_str(tree_element.tag) 367 klass = _class_registry.get(tag, cls) 368 element = klass(tag_or_elem=tree_element) 369 if cache and element._caching: 370 element._tmap = cache[0] 371 element._cmap = cache[1] 372 if len(cache) == 3: 373 element._rmap = cache[2] 374 return element
376 @staticmethod 377 def make_etree_element(tag: str) -> _Element: 378 if not isinstance(tag, str): 379 raise TypeError(f"Tag is not str: {tag!r}") 380 tag = tag.strip() 381 if not tag: 382 raise ValueError("Tag is empty") 383 if "<" not in tag: 384 # Qualified name 385 # XXX don't build the element from scratch or lxml will pollute with 386 # repeated namespace declarations 387 tag = f"<{tag}/>" 388 # XML fragment 389 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 390 return root[0]
753 @property 754 def tag(self) -> str: 755 """Get/set the underlying xml tag with the given qualified name. 756 757 Warning: direct change of tag does not change the element class. 758 759 Arguments: 760 761 qname -- str (e.g. "text:span") 762 """ 763 return _get_prefixed_name(self.__element.tag)
Get/set the underlying xml tag with the given qualified name.
Warning: direct change of tag does not change the element class.
Arguments:
qname -- str (e.g. "text:span")
769 def elements_repeated_sequence( 770 self, 771 xpath_instance: XPath, 772 name: str, 773 ) -> list[tuple[int, int]]: 774 """Utility method for table module.""" 775 lxml_tag = _get_lxml_tag_or_name(name) 776 element = self.__element 777 sub_elements = xpath_instance(element) 778 if not isinstance(sub_elements, list): 779 raise TypeError("Bad XPath result.") 780 result: list[tuple[int, int]] = [] 781 idx = -1 782 for sub_element in sub_elements: 783 if not isinstance(sub_element, _Element): 784 continue 785 idx += 1 786 value = sub_element.get(lxml_tag) 787 if value is None: 788 result.append((idx, 1)) 789 continue 790 try: 791 int_value = int(value) 792 except ValueError: 793 int_value = 1 794 result.append((idx, max(int_value, 1))) 795 return result
Utility method for table module.
797 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 798 cache: tuple | None = None 799 element = self.__element 800 if isinstance(xpath_query, str): 801 new_xpath_query = xpath_compile(xpath_query) 802 result = new_xpath_query(element) 803 else: 804 result = xpath_query(element) 805 if not isinstance(result, list): 806 raise TypeError("Bad XPath result") 807 808 if hasattr(self, "_tmap"): 809 if hasattr(self, "_rmap"): 810 cache = (self._tmap, self._cmap, self._rmap) 811 else: 812 cache = (self._tmap, self._cmap) 813 return [ 814 Element.from_tag_for_clone(e, cache) 815 for e in result 816 if isinstance(e, _Element) 817 ]
849 def get_attribute(self, name: str) -> str | bool | None: 850 """Return the attribute value as type str | bool | None.""" 851 element = self.__element 852 lxml_tag = _get_lxml_tag_or_name(name) 853 value = element.get(lxml_tag) 854 if value is None: 855 return None 856 elif value in ("true", "false"): 857 return Boolean.decode(value) 858 return str(value)
Return the attribute value as type str | bool | None.
860 def get_attribute_integer(self, name: str) -> int | None: 861 """Return either the attribute as type int, or None.""" 862 element = self.__element 863 lxml_tag = _get_lxml_tag_or_name(name) 864 value = element.get(lxml_tag) 865 if value is None: 866 return None 867 try: 868 return int(value) 869 except ValueError: 870 return None
Return either the attribute as type int, or None.
872 def get_attribute_string(self, name: str) -> str | None: 873 """Return either the attribute as type str, or None.""" 874 element = self.__element 875 lxml_tag = _get_lxml_tag_or_name(name) 876 value = element.get(lxml_tag) 877 if value is None: 878 return None 879 return str(value)
Return either the attribute as type str, or None.
881 def set_attribute( 882 self, name: str, value: bool | str | tuple[int, int, int] | None 883 ) -> None: 884 if name in ODF_COLOR_PROPERTY: 885 if isinstance(value, bool): 886 raise TypeError(f"Wrong color type {value!r}") 887 if value != "transparent": 888 value = hexa_color(value) 889 element = self.__element 890 lxml_tag = _get_lxml_tag_or_name(name) 891 if isinstance(value, bool): 892 value = Boolean.encode(value) 893 elif value is None: 894 with contextlib.suppress(KeyError): 895 del element.attrib[lxml_tag] 896 return 897 element.set(lxml_tag, str(value))
899 def set_style_attribute(self, name: str, value: Element | str) -> None: 900 """Shortcut to accept a style object as a value.""" 901 if isinstance(value, Element): 902 value = str(value.name) # type:ignore 903 return self.set_attribute(name, value)
Shortcut to accept a style object as a value.
910 @property 911 def text(self) -> str: 912 """Get / set the text content of the element.""" 913 return self.__element.text or ""
Get / set the text content of the element.
928 @property 929 def tail(self) -> str | None: 930 """Get / set the text immediately following the element.""" 931 return self.__element.tail
Get / set the text immediately following the element.
937 def search(self, pattern: str) -> int | None: 938 """Return the first position of the pattern in the text content of 939 the element, or None if not found. 940 941 Python regular expression syntax applies. 942 943 Arguments: 944 945 pattern -- str 946 947 Return: int or None 948 """ 949 match = re.search(pattern, self.text_recursive) 950 if match is None: 951 return None 952 return match.start()
Return the first position of the pattern in the text content of the element, or None if not found.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: int or None
954 def match(self, pattern: str) -> bool: 955 """return True if the pattern is found one or more times anywhere in 956 the text content of the element. 957 958 Python regular expression syntax applies. 959 960 Arguments: 961 962 pattern -- str 963 964 Return: bool 965 """ 966 return self.search(pattern) is not None
return True if the pattern is found one or more times anywhere in the text content of the element.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: bool
968 def replace(self, pattern: str, new: str | None = None) -> int: 969 """Replace the pattern with the given text, or delete if text is an 970 empty string, and return the number of replacements. By default, only 971 return the number of occurences that would be replaced. 972 973 It cannot replace patterns found across several element, like a word 974 split into two consecutive spans. 975 976 Python regular expression syntax applies. 977 978 Arguments: 979 980 pattern -- str 981 982 new -- str 983 984 Return: int 985 """ 986 if not isinstance(pattern, str): 987 # Fail properly if the pattern is an non-ascii bytestring 988 pattern = str(pattern) 989 cpattern = re.compile(pattern) 990 count = 0 991 for text in self.xpath("descendant::text()"): 992 if new is None: 993 count += len(cpattern.findall(str(text))) 994 else: 995 new_text, number = cpattern.subn(new, str(text)) 996 container = text.parent 997 if text.is_text(): # type: ignore 998 container.text = new_text # type: ignore 999 else: 1000 container.tail = new_text # type: ignore 1001 count += number 1002 return count
Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.
It cannot replace patterns found across several element, like a word split into two consecutive spans.
Python regular expression syntax applies.
Arguments:
pattern -- str
new -- str
Return: int
1047 def index(self, child: Element) -> int: 1048 """Return the position of the child in this element. 1049 1050 Inspired by lxml 1051 """ 1052 return self.__element.index(child.__element)
Return the position of the child in this element.
Inspired by lxml
1054 @property 1055 def text_content(self) -> str: 1056 """Get / set the text of the embedded paragraph, including embeded 1057 annotations, cells... 1058 1059 Set create a paragraph if missing 1060 """ 1061 return "\n".join( 1062 child.text_recursive for child in self.get_elements("descendant::text:p") 1063 )
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
1095 def is_empty(self) -> bool: 1096 """Check if the element is empty : no text, no children, no tail. 1097 1098 Return: Boolean 1099 """ 1100 element = self.__element 1101 if element.tail is not None: 1102 return False 1103 if element.text is not None: 1104 return False 1105 if list(element.iterchildren()): 1106 return False 1107 return True
Check if the element is empty : no text, no children, no tail.
Return: Boolean
1252 def get_between( 1253 self, 1254 tag1: Element, 1255 tag2: Element, 1256 as_text: bool = False, 1257 clean: bool = True, 1258 no_header: bool = True, 1259 ) -> list | Element | str: 1260 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1261 be unique and having an id attribute. 1262 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1263 If as_text is True: returns the text content. 1264 If clean is True: suppress unwanted tags (deletions marks, ...) 1265 If no_header is True: existing text:h are changed in text:p 1266 By default: returns a list of Element, cleaned and without headers. 1267 1268 Implementation and standard retrictions: 1269 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1270 of insert tags are: 1271 1272 - any text:h, text:p or sub tag of these 1273 1274 - some text, part of a parent text:h or text:p 1275 1276 Arguments: 1277 1278 tag1 -- Element 1279 1280 tag2 -- Element 1281 1282 as_text -- boolean 1283 1284 clean -- boolean 1285 1286 no_header -- boolean 1287 1288 Return: list of odf_paragraph or odf_header 1289 """ 1290 inner = self._get_between_base(tag1, tag2) 1291 1292 if clean: 1293 clean_tags = ( 1294 "text:change", 1295 "text:change-start", 1296 "text:change-end", 1297 "text:reference-mark", 1298 "text:reference-mark-start", 1299 "text:reference-mark-end", 1300 ) 1301 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1302 inner = [e for e in inner if not e.xpath(request_self)] 1303 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1304 for element in inner: 1305 to_del = element.xpath(request) 1306 for elem in to_del: 1307 if isinstance(elem, Element): 1308 element.delete(elem) 1309 if no_header: # crude replace t:h by t:p 1310 new_inner = [] 1311 for element in inner: 1312 if element.tag == "text:h": 1313 children = element.children 1314 text = element.__element.text 1315 para = Element.from_tag("text:p") 1316 para.text = text or "" 1317 for c in children: 1318 para.append(c) 1319 new_inner.append(para) 1320 else: 1321 new_inner.append(element) 1322 inner = new_inner 1323 if as_text: 1324 return "\n".join([e.get_formatted_text() for e in inner]) 1325 else: 1326 return inner
Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.
Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:
- any text:h, text:p or sub tag of these
- some text, part of a parent text:h or text:p
Arguments:
tag1 -- Element
tag2 -- Element
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list of odf_paragraph or odf_header
1328 def insert( 1329 self, 1330 element: Element, 1331 xmlposition: int | None = None, 1332 position: int | None = None, 1333 start: bool = False, 1334 ) -> None: 1335 """Insert an element relatively to ourself. 1336 1337 Insert either using DOM vocabulary or by numeric position. 1338 If text start is True, insert the element before any existing text. 1339 1340 Position start at 0. 1341 1342 Arguments: 1343 1344 element -- Element 1345 1346 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1347 or PREV_SIBLING 1348 1349 start -- Boolean 1350 1351 position -- int 1352 """ 1353 # child_tag = element.tag 1354 current = self.__element 1355 _element = element.__element 1356 if start: 1357 text = current.text 1358 if text is not None: 1359 current.text = None 1360 tail = _element.tail 1361 if tail is None: 1362 tail = text 1363 else: 1364 tail = tail + text 1365 _element.tail = tail 1366 position = 0 1367 if position is not None: 1368 current.insert(position, _element) 1369 elif xmlposition is FIRST_CHILD: 1370 current.insert(0, _element) 1371 elif xmlposition is LAST_CHILD: 1372 current.append(_element) 1373 elif xmlposition is NEXT_SIBLING: 1374 parent = current.getparent() 1375 index = parent.index(current) # type: ignore 1376 parent.insert(index + 1, _element) # type: ignore 1377 elif xmlposition is PREV_SIBLING: 1378 parent = current.getparent() 1379 index = parent.index(current) # type: ignore 1380 parent.insert(index, _element) # type: ignore 1381 else: 1382 raise ValueError("(xml)position must be defined")
Insert an element relatively to ourself.
Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.
Position start at 0.
Arguments:
element -- Element
xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
or PREV_SIBLING
start -- Boolean
position -- int
1384 def extend(self, odf_elements: Iterable[Element]) -> None: 1385 """Fast append elements at the end of ourself using extend.""" 1386 if odf_elements: 1387 current = self.__element 1388 elements = [element.__element for element in odf_elements] 1389 current.extend(elements)
Fast append elements at the end of ourself using extend.
1391 def append(self, str_or_element: str | Element) -> None: 1392 """Insert element or text in the last position.""" 1393 current = self.__element 1394 if isinstance(str_or_element, str): 1395 # Has children ? 1396 children = list(current.iterchildren()) 1397 if children: 1398 # Append to tail of the last child 1399 last_child = children[-1] 1400 text = last_child.tail 1401 text = text if text is not None else "" 1402 text += str_or_element 1403 last_child.tail = text 1404 else: 1405 # Append to text of the element 1406 text = current.text 1407 text = text if text is not None else "" 1408 text += str_or_element 1409 current.text = text 1410 elif isinstance(str_or_element, Element): 1411 current.append(str_or_element.__element) 1412 else: 1413 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
Insert element or text in the last position.
1415 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1416 """Delete the given element from the XML tree. If no element is given, 1417 "self" is deleted. The XML library may allow to continue to use an 1418 element now "orphan" as long as you have a reference to it. 1419 1420 if keep_tail is True (default), the tail text is not erased. 1421 1422 Arguments: 1423 1424 child -- Element 1425 1426 keep_tail -- boolean (default to True), True for most usages. 1427 """ 1428 if child is None: 1429 parent = self.parent 1430 if parent is None: 1431 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1432 child = self 1433 else: 1434 parent = self 1435 if keep_tail and child.__element.tail is not None: 1436 current = child.__element 1437 tail = str(current.tail) 1438 current.tail = None 1439 prev = current.getprevious() 1440 if prev is not None: 1441 if prev.tail is None: 1442 prev.tail = tail 1443 else: 1444 prev.tail += tail 1445 else: 1446 if parent.__element.text is None: 1447 parent.__element.text = tail 1448 else: 1449 parent.__element.text += tail 1450 parent.__element.remove(child.__element)
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
if keep_tail is True (default), the tail text is not erased.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
1452 def replace_element(self, old_element: Element, new_element: Element) -> None: 1453 """Replaces in place a sub element with the element passed as second 1454 argument. 1455 1456 Warning : no clone for old element. 1457 """ 1458 current = self.__element 1459 current.replace(old_element.__element, new_element.__element)
Replaces in place a sub element with the element passed as second argument.
Warning : no clone for old element.
1461 def strip_elements( 1462 self, 1463 sub_elements: Element | Iterable[Element], 1464 ) -> Element | list: 1465 """Remove the tags of provided elements, keeping inner childs and text. 1466 1467 Return : the striped element. 1468 1469 Warning : no clone in sub_elements list. 1470 1471 Arguments: 1472 1473 sub_elements -- Element or list of Element 1474 """ 1475 if not sub_elements: 1476 return self 1477 if isinstance(sub_elements, Element): 1478 sub_elements = (sub_elements,) 1479 replacer = _get_lxml_tag("text:this-will-be-removed") 1480 for element in sub_elements: 1481 element.__element.tag = replacer 1482 strip = ("text:this-will-be-removed",) 1483 return self.strip_tags(strip=strip, default=None)
Remove the tags of provided elements, keeping inner childs and text.
Return : the striped element.
Warning : no clone in sub_elements list.
Arguments:
sub_elements -- Element or list of Element
1583 def xpath(self, xpath_query: str) -> list[Element | Text]: 1584 """Apply XPath query to the element and its subtree. Return list of 1585 Element or Text instances translated from the nodes found. 1586 """ 1587 element = self.__element 1588 xpath_instance = xpath_compile(xpath_query) 1589 elements = xpath_instance(element) 1590 result: list[Element | Text] = [] 1591 if hasattr(elements, "__iter__"): 1592 for obj in elements: # type: ignore 1593 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1594 result.append(Text(obj)) 1595 elif isinstance(obj, _Element): 1596 result.append(Element.from_tag(obj)) 1597 # else: 1598 # result.append(obj) 1599 return result
Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.
1601 def clear(self) -> None: 1602 """Remove text, children and attributes from the element.""" 1603 self.__element.clear() 1604 if hasattr(self, "_tmap"): 1605 self._tmap: list[int] = [] 1606 if hasattr(self, "_cmap"): 1607 self._cmap: list[int] = [] 1608 if hasattr(self, "_rmap"): 1609 self._rmap: list[int] = [] 1610 if hasattr(self, "_indexes"): 1611 remember = False 1612 if "_rmap" in self._indexes: 1613 remember = True 1614 self._indexes: dict[str, dict] = {} 1615 self._indexes["_cmap"] = {} 1616 self._indexes["_tmap"] = {} 1617 if remember: 1618 self._indexes["_rmap"] = {}
Remove text, children and attributes from the element.
1620 @property 1621 def clone(self) -> Element: 1622 clone = deepcopy(self.__element) 1623 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1624 root.append(clone) 1625 return self.from_tag(clone) 1626 1627 # slow data = tostring(self.__element, encoding='unicode') 1628 # return self.from_tag(data)
1635 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1636 """Return text serialization of XML element.""" 1637 # This copy bypasses serialization side-effects in lxml 1638 native = deepcopy(self.__element) 1639 data = tostring( 1640 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1641 ) 1642 if with_ns: 1643 return data 1644 # Remove namespaces 1645 return self._strip_namespaces(data)
Return text serialization of XML element.
1649 @property 1650 def document_body(self) -> Element | None: 1651 """Return the document body : 'office:body'""" 1652 return self.get_element("//office:body/*[1]")
Return the document body : 'office:body'
1667 def get_formatted_text(self, context: dict | None = None) -> str: 1668 """This function should return a beautiful version of the text.""" 1669 return ""
This function should return a beautiful version of the text.
1671 def get_styled_elements(self, name: str = "") -> list[Element]: 1672 """Brute-force to find paragraphs, tables, etc. using the given style 1673 name (or all by default). 1674 1675 Arguments: 1676 1677 name -- str 1678 1679 Return: list 1680 """ 1681 # FIXME incomplete (and possibly inaccurate) 1682 return ( 1683 self._filtered_elements("descendant::*", text_style=name) 1684 + self._filtered_elements("descendant::*", draw_style=name) 1685 + self._filtered_elements("descendant::*", draw_text_style=name) 1686 + self._filtered_elements("descendant::*", table_style=name) 1687 + self._filtered_elements("descendant::*", page_layout=name) 1688 + self._filtered_elements("descendant::*", master_page=name) 1689 + self._filtered_elements("descendant::*", parent_style=name) 1690 )
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
1709 @property 1710 def dc_creator(self) -> str | None: 1711 """Get dc:creator value. 1712 1713 Return: str (or None if inexistant) 1714 """ 1715 return self._get_inner_text("dc:creator")
Get dc:creator value.
Return: str (or None if inexistant)
1727 @property 1728 def dc_date(self) -> datetime | None: 1729 """Get the dc:date value. 1730 1731 Return: datetime (or None if inexistant) 1732 """ 1733 date = self._get_inner_text("dc:date") 1734 if date is None: 1735 return None 1736 return DateTime.decode(date)
Get the dc:date value.
Return: datetime (or None if inexistant)
1768 def get_sections( 1769 self, 1770 style: str | None = None, 1771 content: str | None = None, 1772 ) -> list[Element]: 1773 """Return all the sections that match the criteria. 1774 1775 Arguments: 1776 1777 style -- str 1778 1779 content -- str regex 1780 1781 Return: list of Element 1782 """ 1783 return self._filtered_elements( 1784 "text:section", text_style=style, content=content 1785 )
Return all the sections that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
1787 def get_section( 1788 self, 1789 position: int = 0, 1790 content: str | None = None, 1791 ) -> Element | None: 1792 """Return the section that matches the criteria. 1793 1794 Arguments: 1795 1796 position -- int 1797 1798 content -- str regex 1799 1800 Return: Element or None if not found 1801 """ 1802 return self._filtered_element( 1803 "descendant::text:section", position, content=content 1804 )
Return the section that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
1808 def get_paragraphs( 1809 self, 1810 style: str | None = None, 1811 content: str | None = None, 1812 ) -> list[Element]: 1813 """Return all the paragraphs that match the criteria. 1814 1815 Arguments: 1816 1817 style -- str 1818 1819 content -- str regex 1820 1821 Return: list of Paragraph 1822 """ 1823 return self._filtered_elements( 1824 "descendant::text:p", text_style=style, content=content 1825 )
Return all the paragraphs that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Paragraph
1827 def get_paragraph( 1828 self, 1829 position: int = 0, 1830 content: str | None = None, 1831 ) -> Element | None: 1832 """Return the paragraph that matches the criteria. 1833 1834 Arguments: 1835 1836 position -- int 1837 1838 content -- str regex 1839 1840 Return: Paragraph or None if not found 1841 """ 1842 return self._filtered_element("descendant::text:p", position, content=content)
Return the paragraph that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Paragraph or None if not found
1846 def get_spans( 1847 self, 1848 style: str | None = None, 1849 content: str | None = None, 1850 ) -> list[Element]: 1851 """Return all the spans that match the criteria. 1852 1853 Arguments: 1854 1855 style -- str 1856 1857 content -- str regex 1858 1859 Return: list of Span 1860 """ 1861 return self._filtered_elements( 1862 "descendant::text:span", text_style=style, content=content 1863 )
Return all the spans that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Span
1865 def get_span( 1866 self, 1867 position: int = 0, 1868 content: str | None = None, 1869 ) -> Element | None: 1870 """Return the span that matches the criteria. 1871 1872 Arguments: 1873 1874 position -- int 1875 1876 content -- str regex 1877 1878 Return: Span or None if not found 1879 """ 1880 return self._filtered_element( 1881 "descendant::text:span", position, content=content 1882 )
Return the span that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Span or None if not found
1886 def get_headers( 1887 self, 1888 style: str | None = None, 1889 outline_level: str | None = None, 1890 content: str | None = None, 1891 ) -> list[Element]: 1892 """Return all the Headers that match the criteria. 1893 1894 Arguments: 1895 1896 style -- str 1897 1898 content -- str regex 1899 1900 Return: list of Header 1901 """ 1902 return self._filtered_elements( 1903 "descendant::text:h", 1904 text_style=style, 1905 outline_level=outline_level, 1906 content=content, 1907 )
Return all the Headers that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Header
1909 def get_header( 1910 self, 1911 position: int = 0, 1912 outline_level: str | None = None, 1913 content: str | None = None, 1914 ) -> Element | None: 1915 """Return the Header that matches the criteria. 1916 1917 Arguments: 1918 1919 position -- int 1920 1921 content -- str regex 1922 1923 Return: Header or None if not found 1924 """ 1925 return self._filtered_element( 1926 "descendant::text:h", 1927 position, 1928 outline_level=outline_level, 1929 content=content, 1930 )
Return the Header that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Header or None if not found
1934 def get_lists( 1935 self, 1936 style: str | None = None, 1937 content: str | None = None, 1938 ) -> list[Element]: 1939 """Return all the lists that match the criteria. 1940 1941 Arguments: 1942 1943 style -- str 1944 1945 content -- str regex 1946 1947 Return: list of List 1948 """ 1949 return self._filtered_elements( 1950 "descendant::text:list", text_style=style, content=content 1951 )
Return all the lists that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of List
1953 def get_list( 1954 self, 1955 position: int = 0, 1956 content: str | None = None, 1957 ) -> Element | None: 1958 """Return the list that matches the criteria. 1959 1960 Arguments: 1961 1962 position -- int 1963 1964 content -- str regex 1965 1966 Return: List or None if not found 1967 """ 1968 return self._filtered_element( 1969 "descendant::text:list", position, content=content 1970 )
Return the list that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: List or None if not found
1974 def get_frames( 1975 self, 1976 presentation_class: str | None = None, 1977 style: str | None = None, 1978 title: str | None = None, 1979 description: str | None = None, 1980 content: str | None = None, 1981 ) -> list[Element]: 1982 """Return all the frames that match the criteria. 1983 1984 Arguments: 1985 1986 presentation_class -- str 1987 1988 style -- str 1989 1990 title -- str regex 1991 1992 description -- str regex 1993 1994 content -- str regex 1995 1996 Return: list of Frame 1997 """ 1998 return self._filtered_elements( 1999 "descendant::draw:frame", 2000 presentation_class=presentation_class, 2001 draw_style=style, 2002 svg_title=title, 2003 svg_desc=description, 2004 content=content, 2005 )
Return all the frames that match the criteria.
Arguments:
presentation_class -- str
style -- str
title -- str regex
description -- str regex
content -- str regex
Return: list of Frame
2007 def get_frame( 2008 self, 2009 position: int = 0, 2010 name: str | None = None, 2011 presentation_class: str | None = None, 2012 title: str | None = None, 2013 description: str | None = None, 2014 content: str | None = None, 2015 ) -> Element | None: 2016 """Return the section that matches the criteria. 2017 2018 Arguments: 2019 2020 position -- int 2021 2022 name -- str 2023 2024 presentation_class -- str 2025 2026 title -- str regex 2027 2028 description -- str regex 2029 2030 content -- str regex 2031 2032 Return: Frame or None if not found 2033 """ 2034 return self._filtered_element( 2035 "descendant::draw:frame", 2036 position, 2037 draw_name=name, 2038 presentation_class=presentation_class, 2039 svg_title=title, 2040 svg_desc=description, 2041 content=content, 2042 )
Return the section that matches the criteria.
Arguments:
position -- int
name -- str
presentation_class -- str
title -- str regex
description -- str regex
content -- str regex
Return: Frame or None if not found
2046 def get_images( 2047 self, 2048 style: str | None = None, 2049 url: str | None = None, 2050 content: str | None = None, 2051 ) -> list[Element]: 2052 """Return all the images matching the criteria. 2053 2054 Arguments: 2055 2056 style -- str 2057 2058 url -- str regex 2059 2060 content -- str regex 2061 2062 Return: list of Element 2063 """ 2064 return self._filtered_elements( 2065 "descendant::draw:image", text_style=style, url=url, content=content 2066 )
Return all the images matching the criteria.
Arguments:
style -- str
url -- str regex
content -- str regex
Return: list of Element
2068 def get_image( 2069 self, 2070 position: int = 0, 2071 name: str | None = None, 2072 url: str | None = None, 2073 content: str | None = None, 2074 ) -> Element | None: 2075 """Return the image matching the criteria. 2076 2077 Arguments: 2078 2079 position -- int 2080 2081 name -- str 2082 2083 url -- str regex 2084 2085 content -- str regex 2086 2087 Return: Element or None if not found 2088 """ 2089 # The frame is holding the name 2090 if name is not None: 2091 frame = self._filtered_element( 2092 "descendant::draw:frame", position, draw_name=name 2093 ) 2094 if frame is None: 2095 return None 2096 # The name is supposedly unique 2097 return frame.get_element("draw:image") 2098 return self._filtered_element( 2099 "descendant::draw:image", position, url=url, content=content 2100 )
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2104 def get_tables( 2105 self, 2106 style: str | None = None, 2107 content: str | None = None, 2108 ) -> list[Element]: 2109 """Return all the tables that match the criteria. 2110 2111 Arguments: 2112 2113 style -- str 2114 2115 content -- str regex 2116 2117 Return: list of Table 2118 """ 2119 return self._filtered_elements( 2120 "descendant::table:table", table_style=style, content=content 2121 )
Return all the tables that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Table
2123 def get_table( 2124 self, 2125 position: int = 0, 2126 name: str | None = None, 2127 content: str | None = None, 2128 ) -> Element | None: 2129 """Return the table that matches the criteria. 2130 2131 Arguments: 2132 2133 position -- int 2134 2135 name -- str 2136 2137 content -- str regex 2138 2139 Return: Table or None if not found 2140 """ 2141 if name is None and content is None: 2142 result = self._filtered_element("descendant::table:table", position) 2143 else: 2144 result = self._filtered_element( 2145 "descendant::table:table", 2146 position, 2147 table_name=name, 2148 content=content, 2149 ) 2150 return result
Return the table that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: Table or None if not found
2154 def get_named_ranges(self) -> list[Element]: 2155 """Return all the tables named ranges. 2156 2157 Return: list of odf_named_range 2158 """ 2159 named_ranges = self.get_elements( 2160 "descendant::table:named-expressions/table:named-range" 2161 ) 2162 return named_ranges
Return all the tables named ranges.
Return: list of odf_named_range
2164 def get_named_range(self, name: str) -> Element | None: 2165 """Return the named range of specified name, or None if not found. 2166 2167 Arguments: 2168 2169 name -- str 2170 2171 Return: NamedRange 2172 """ 2173 named_range = self.get_elements( 2174 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2175 ) 2176 if named_range: 2177 return named_range[0] 2178 else: 2179 return None
Return the named range of specified name, or None if not found.
Arguments:
name -- str
Return: NamedRange
2181 def append_named_range(self, named_range: Element) -> None: 2182 """Append the named range to the spreadsheet, replacing existing named 2183 range of same name if any. 2184 2185 Arguments: 2186 2187 named_range -- NamedRange 2188 """ 2189 if self.tag != "office:spreadsheet": 2190 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2191 named_expressions = self.get_element("table:named-expressions") 2192 if not named_expressions: 2193 named_expressions = Element.from_tag("table:named-expressions") 2194 self.append(named_expressions) 2195 # exists ? 2196 current = named_expressions.get_element( 2197 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2198 ) 2199 if current: 2200 named_expressions.delete(current) 2201 named_expressions.append(named_range)
Append the named range to the spreadsheet, replacing existing named range of same name if any.
Arguments:
named_range -- NamedRange
2203 def delete_named_range(self, name: str) -> None: 2204 """Delete the Named Range of specified name from the spreadsheet. 2205 2206 Arguments: 2207 2208 name -- str 2209 """ 2210 if self.tag != "office:spreadsheet": 2211 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2212 named_range = self.get_named_range(name) 2213 if not named_range: 2214 return 2215 named_range.delete() 2216 named_expressions = self.get_element("table:named-expressions") 2217 if not named_expressions: 2218 return 2219 element = named_expressions.__element 2220 children = list(element.iterchildren()) 2221 if not children: 2222 self.delete(named_expressions)
Delete the Named Range of specified name from the spreadsheet.
Arguments:
name -- str
2226 def get_notes( 2227 self, 2228 note_class: str | None = None, 2229 content: str | None = None, 2230 ) -> list[Element]: 2231 """Return all the notes that match the criteria. 2232 2233 Arguments: 2234 2235 note_class -- 'footnote' or 'endnote' 2236 2237 content -- str regex 2238 2239 Return: list of Note 2240 """ 2241 return self._filtered_elements( 2242 "descendant::text:note", note_class=note_class, content=content 2243 )
Return all the notes that match the criteria.
Arguments:
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: list of Note
2245 def get_note( 2246 self, 2247 position: int = 0, 2248 note_id: str | None = None, 2249 note_class: str | None = None, 2250 content: str | None = None, 2251 ) -> Element | None: 2252 """Return the note that matches the criteria. 2253 2254 Arguments: 2255 2256 position -- int 2257 2258 note_id -- str 2259 2260 note_class -- 'footnote' or 'endnote' 2261 2262 content -- str regex 2263 2264 Return: Note or None if not found 2265 """ 2266 return self._filtered_element( 2267 "descendant::text:note", 2268 position, 2269 text_id=note_id, 2270 note_class=note_class, 2271 content=content, 2272 )
Return the note that matches the criteria.
Arguments:
position -- int
note_id -- str
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: Note or None if not found
2276 def get_annotations( 2277 self, 2278 creator: str | None = None, 2279 start_date: datetime | None = None, 2280 end_date: datetime | None = None, 2281 content: str | None = None, 2282 ) -> list[Element]: 2283 """Return all the annotations that match the criteria. 2284 2285 Arguments: 2286 2287 creator -- str 2288 2289 start_date -- datetime instance 2290 2291 end_date -- datetime instance 2292 2293 content -- str regex 2294 2295 Return: list of Annotation 2296 """ 2297 annotations = [] 2298 for annotation in self._filtered_elements( 2299 "descendant::office:annotation", content=content 2300 ): 2301 if creator is not None and creator != annotation.dc_creator: 2302 continue 2303 date = annotation.dc_date 2304 if date is None: 2305 continue 2306 if start_date is not None and date < start_date: 2307 continue 2308 if end_date is not None and date >= end_date: 2309 continue 2310 annotations.append(annotation) 2311 return annotations
Return all the annotations that match the criteria.
Arguments:
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
Return: list of Annotation
2313 def get_annotation( 2314 self, 2315 position: int = 0, 2316 creator: str | None = None, 2317 start_date: datetime | None = None, 2318 end_date: datetime | None = None, 2319 content: str | None = None, 2320 name: str | None = None, 2321 ) -> Element | None: 2322 """Return the annotation that matches the criteria. 2323 2324 Arguments: 2325 2326 position -- int 2327 2328 creator -- str 2329 2330 start_date -- datetime instance 2331 2332 end_date -- datetime instance 2333 2334 content -- str regex 2335 2336 name -- str 2337 2338 Return: Annotation or None if not found 2339 """ 2340 if name is not None: 2341 return self._filtered_element( 2342 "descendant::office:annotation", 0, office_name=name 2343 ) 2344 annotations = self.get_annotations( 2345 creator=creator, start_date=start_date, end_date=end_date, content=content 2346 ) 2347 if not annotations: 2348 return None 2349 try: 2350 return annotations[position] 2351 except IndexError: 2352 return None
Return the annotation that matches the criteria.
Arguments:
position -- int
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
name -- str
Return: Annotation or None if not found
2354 def get_annotation_ends(self) -> list[Element]: 2355 """Return all the annotation ends. 2356 2357 Return: list of Element 2358 """ 2359 return self._filtered_elements("descendant::office:annotation-end")
Return all the annotation ends.
Return: list of Element
2361 def get_annotation_end( 2362 self, 2363 position: int = 0, 2364 name: str | None = None, 2365 ) -> Element | None: 2366 """Return the annotation end that matches the criteria. 2367 2368 Arguments: 2369 2370 position -- int 2371 2372 name -- str 2373 2374 Return: Element or None if not found 2375 """ 2376 return self._filtered_element( 2377 "descendant::office:annotation-end", position, office_name=name 2378 )
Return the annotation end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2382 def get_office_names(self) -> list[str]: 2383 """Return all the used office:name tags values of the element. 2384 2385 Return: list of unique str 2386 """ 2387 name_xpath_query = xpath_compile("//@office:name") 2388 response = name_xpath_query(self.__element) 2389 if not isinstance(response, list): 2390 return [] 2391 return list({str(name) for name in response if name})
Return all the used office:name tags values of the element.
Return: list of unique str
2395 def get_variable_decls(self) -> Element: 2396 """Return the container for variable declarations. Created if not 2397 found. 2398 2399 Return: Element 2400 """ 2401 variable_decls = self.get_element("//text:variable-decls") 2402 if variable_decls is None: 2403 body = self.document_body 2404 if not body: 2405 raise ValueError("Empty document.body") 2406 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2407 variable_decls = body.get_element("//text:variable-decls") 2408 2409 return variable_decls # type:ignore
Return the container for variable declarations. Created if not found.
Return: Element
2411 def get_variable_decl_list(self) -> list[Element]: 2412 """Return all the variable declarations. 2413 2414 Return: list of Element 2415 """ 2416 return self._filtered_elements("descendant::text:variable-decl")
Return all the variable declarations.
Return: list of Element
2418 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2419 """return the variable declaration for the given name. 2420 2421 Arguments: 2422 2423 name -- str 2424 2425 position -- int 2426 2427 return: Element or none if not found 2428 """ 2429 return self._filtered_element( 2430 "descendant::text:variable-decl", position, text_name=name 2431 )
return the variable declaration for the given name.
Arguments:
name -- str
position -- int
return: Element or none if not found
2433 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2434 """Return all the variable sets that match the criteria. 2435 2436 Arguments: 2437 2438 name -- str 2439 2440 Return: list of Element 2441 """ 2442 return self._filtered_elements("descendant::text:variable-set", text_name=name)
Return all the variable sets that match the criteria.
Arguments:
name -- str
Return: list of Element
2444 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2445 """Return the variable set for the given name (last one by default). 2446 2447 Arguments: 2448 2449 name -- str 2450 2451 position -- int 2452 2453 Return: Element or None if not found 2454 """ 2455 return self._filtered_element( 2456 "descendant::text:variable-set", position, text_name=name 2457 )
Return the variable set for the given name (last one by default).
Arguments:
name -- str
position -- int
Return: Element or None if not found
2459 def get_variable_set_value( 2460 self, 2461 name: str, 2462 value_type: str | None = None, 2463 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2464 """Return the last value of the given variable name. 2465 2466 Arguments: 2467 2468 name -- str 2469 2470 value_type -- 'boolean', 'currency', 'date', 'float', 2471 'percentage', 'string', 'time' or automatic 2472 2473 Return: most appropriate Python type 2474 """ 2475 variable_set = self.get_variable_set(name) 2476 if not variable_set: 2477 return None 2478 return variable_set.get_value(value_type) # type: ignore
Return the last value of the given variable name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2482 def get_user_field_decls(self) -> Element | None: 2483 """Return the container for user field declarations. Created if not 2484 found. 2485 2486 Return: Element 2487 """ 2488 user_field_decls = self.get_element("//text:user-field-decls") 2489 if user_field_decls is None: 2490 body = self.document_body 2491 if not body: 2492 raise ValueError("Empty document.body") 2493 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2494 user_field_decls = body.get_element("//text:user-field-decls") 2495 2496 return user_field_decls
Return the container for user field declarations. Created if not found.
Return: Element
2498 def get_user_field_decl_list(self) -> list[Element]: 2499 """Return all the user field declarations. 2500 2501 Return: list of Element 2502 """ 2503 return self._filtered_elements("descendant::text:user-field-decl")
Return all the user field declarations.
Return: list of Element
2505 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2506 """return the user field declaration for the given name. 2507 2508 return: Element or none if not found 2509 """ 2510 return self._filtered_element( 2511 "descendant::text:user-field-decl", position, text_name=name 2512 )
return the user field declaration for the given name.
return: Element or none if not found
2514 def get_user_field_value( 2515 self, name: str, value_type: str | None = None 2516 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2517 """Return the value of the given user field name. 2518 2519 Arguments: 2520 2521 name -- str 2522 2523 value_type -- 'boolean', 'currency', 'date', 'float', 2524 'percentage', 'string', 'time' or automatic 2525 2526 Return: most appropriate Python type 2527 """ 2528 user_field_decl = self.get_user_field_decl(name) 2529 if user_field_decl is None: 2530 return None 2531 return user_field_decl.get_value(value_type) # type: ignore
Return the value of the given user field name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2536 def get_user_defined_list(self) -> list[Element]: 2537 """Return all the user defined field declarations. 2538 2539 Return: list of Element 2540 """ 2541 return self._filtered_elements("descendant::text:user-defined")
Return all the user defined field declarations.
Return: list of Element
2543 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2544 """return the user defined declaration for the given name. 2545 2546 return: Element or none if not found 2547 """ 2548 return self._filtered_element( 2549 "descendant::text:user-defined", position, text_name=name 2550 )
return the user defined declaration for the given name.
return: Element or none if not found
2552 def get_user_defined_value( 2553 self, name: str, value_type: str | None = None 2554 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2555 """Return the value of the given user defined field name. 2556 2557 Arguments: 2558 2559 name -- str 2560 2561 value_type -- 'boolean', 'date', 'float', 2562 'string', 'time' or automatic 2563 2564 Return: most appropriate Python type 2565 """ 2566 user_defined = self.get_user_defined(name) 2567 if user_defined is None: 2568 return None 2569 return user_defined.get_value(value_type) # type: ignore
Return the value of the given user defined field name.
Arguments:
name -- str
value_type -- 'boolean', 'date', 'float',
'string', 'time' or automatic
Return: most appropriate Python type
2573 def get_draw_pages( 2574 self, 2575 style: str | None = None, 2576 content: str | None = None, 2577 ) -> list[Element]: 2578 """Return all the draw pages that match the criteria. 2579 2580 Arguments: 2581 2582 style -- str 2583 2584 content -- str regex 2585 2586 Return: list of DrawPage 2587 """ 2588 return self._filtered_elements( 2589 "descendant::draw:page", draw_style=style, content=content 2590 )
Return all the draw pages that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of DrawPage
2592 def get_draw_page( 2593 self, 2594 position: int = 0, 2595 name: str | None = None, 2596 content: str | None = None, 2597 ) -> Element | None: 2598 """Return the draw page that matches the criteria. 2599 2600 Arguments: 2601 2602 position -- int 2603 2604 name -- str 2605 2606 content -- str regex 2607 2608 Return: DrawPage or None if not found 2609 """ 2610 return self._filtered_element( 2611 "descendant::draw:page", position, draw_name=name, content=content 2612 )
Return the draw page that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: DrawPage or None if not found
2616 def get_links( 2617 self, 2618 name: str | None = None, 2619 title: str | None = None, 2620 url: str | None = None, 2621 content: str | None = None, 2622 ) -> list[Element]: 2623 """Return all the links that match the criteria. 2624 2625 Arguments: 2626 2627 name -- str 2628 2629 title -- str 2630 2631 url -- str regex 2632 2633 content -- str regex 2634 2635 Return: list of Element 2636 """ 2637 return self._filtered_elements( 2638 "descendant::text:a", 2639 office_name=name, 2640 office_title=title, 2641 url=url, 2642 content=content, 2643 )
Return all the links that match the criteria.
Arguments:
name -- str
title -- str
url -- str regex
content -- str regex
Return: list of Element
2645 def get_link( 2646 self, 2647 position: int = 0, 2648 name: str | None = None, 2649 title: str | None = None, 2650 url: str | None = None, 2651 content: str | None = None, 2652 ) -> Element | None: 2653 """Return the link that matches the criteria. 2654 2655 Arguments: 2656 2657 position -- int 2658 2659 name -- str 2660 2661 title -- str 2662 2663 url -- str regex 2664 2665 content -- str regex 2666 2667 Return: Element or None if not found 2668 """ 2669 return self._filtered_element( 2670 "descendant::text:a", 2671 position, 2672 office_name=name, 2673 office_title=title, 2674 url=url, 2675 content=content, 2676 )
Return the link that matches the criteria.
Arguments:
position -- int
name -- str
title -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2680 def get_bookmarks(self) -> list[Element]: 2681 """Return all the bookmarks. 2682 2683 Return: list of Element 2684 """ 2685 return self._filtered_elements("descendant::text:bookmark")
Return all the bookmarks.
Return: list of Element
2687 def get_bookmark( 2688 self, 2689 position: int = 0, 2690 name: str | None = None, 2691 ) -> Element | None: 2692 """Return the bookmark that matches the criteria. 2693 2694 Arguments: 2695 2696 position -- int 2697 2698 name -- str 2699 2700 Return: Bookmark or None if not found 2701 """ 2702 return self._filtered_element( 2703 "descendant::text:bookmark", position, text_name=name 2704 )
Return the bookmark that matches the criteria.
Arguments:
position -- int
name -- str
Return: Bookmark or None if not found
2706 def get_bookmark_starts(self) -> list[Element]: 2707 """Return all the bookmark starts. 2708 2709 Return: list of Element 2710 """ 2711 return self._filtered_elements("descendant::text:bookmark-start")
Return all the bookmark starts.
Return: list of Element
2713 def get_bookmark_start( 2714 self, 2715 position: int = 0, 2716 name: str | None = None, 2717 ) -> Element | None: 2718 """Return the bookmark start that matches the criteria. 2719 2720 Arguments: 2721 2722 position -- int 2723 2724 name -- str 2725 2726 Return: Element or None if not found 2727 """ 2728 return self._filtered_element( 2729 "descendant::text:bookmark-start", position, text_name=name 2730 )
Return the bookmark start that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2732 def get_bookmark_ends(self) -> list[Element]: 2733 """Return all the bookmark ends. 2734 2735 Return: list of Element 2736 """ 2737 return self._filtered_elements("descendant::text:bookmark-end")
Return all the bookmark ends.
Return: list of Element
2739 def get_bookmark_end( 2740 self, 2741 position: int = 0, 2742 name: str | None = None, 2743 ) -> Element | None: 2744 """Return the bookmark end that matches the criteria. 2745 2746 Arguments: 2747 2748 position -- int 2749 2750 name -- str 2751 2752 Return: Element or None if not found 2753 """ 2754 return self._filtered_element( 2755 "descendant::text:bookmark-end", position, text_name=name 2756 )
Return the bookmark end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2760 def get_reference_marks_single(self) -> list[Element]: 2761 """Return all the reference marks. Search only the tags 2762 text:reference-mark. 2763 Consider using : get_reference_marks() 2764 2765 Return: list of Element 2766 """ 2767 return self._filtered_elements("descendant::text:reference-mark")
Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()
Return: list of Element
2769 def get_reference_mark_single( 2770 self, 2771 position: int = 0, 2772 name: str | None = None, 2773 ) -> Element | None: 2774 """Return the reference mark that matches the criteria. Search only the 2775 tags text:reference-mark. 2776 Consider using : get_reference_mark() 2777 2778 Arguments: 2779 2780 position -- int 2781 2782 name -- str 2783 2784 Return: Element or None if not found 2785 """ 2786 return self._filtered_element( 2787 "descendant::text:reference-mark", position, text_name=name 2788 )
Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2790 def get_reference_mark_starts(self) -> list[Element]: 2791 """Return all the reference mark starts. Search only the tags 2792 text:reference-mark-start. 2793 Consider using : get_reference_marks() 2794 2795 Return: list of Element 2796 """ 2797 return self._filtered_elements("descendant::text:reference-mark-start")
Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()
Return: list of Element
2799 def get_reference_mark_start( 2800 self, 2801 position: int = 0, 2802 name: str | None = None, 2803 ) -> Element | None: 2804 """Return the reference mark start that matches the criteria. Search 2805 only the tags text:reference-mark-start. 2806 Consider using : get_reference_mark() 2807 2808 Arguments: 2809 2810 position -- int 2811 2812 name -- str 2813 2814 Return: Element or None if not found 2815 """ 2816 return self._filtered_element( 2817 "descendant::text:reference-mark-start", position, text_name=name 2818 )
Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2820 def get_reference_mark_ends(self) -> list[Element]: 2821 """Return all the reference mark ends. Search only the tags 2822 text:reference-mark-end. 2823 Consider using : get_reference_marks() 2824 2825 Return: list of Element 2826 """ 2827 return self._filtered_elements("descendant::text:reference-mark-end")
Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Return: list of Element
2829 def get_reference_mark_end( 2830 self, 2831 position: int = 0, 2832 name: str | None = None, 2833 ) -> Element | None: 2834 """Return the reference mark end that matches the criteria. Search only 2835 the tags text:reference-mark-end. 2836 Consider using : get_reference_marks() 2837 2838 Arguments: 2839 2840 position -- int 2841 2842 name -- str 2843 2844 Return: Element or None if not found 2845 """ 2846 return self._filtered_element( 2847 "descendant::text:reference-mark-end", position, text_name=name 2848 )
Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2850 def get_reference_marks(self) -> list[Element]: 2851 """Return all the reference marks, either single position reference 2852 (text:reference-mark) or start of range reference 2853 (text:reference-mark-start). 2854 2855 Return: list of Element 2856 """ 2857 return self._filtered_elements( 2858 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2859 )
Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).
Return: list of Element
2861 def get_reference_mark( 2862 self, 2863 position: int = 0, 2864 name: str | None = None, 2865 ) -> Element | None: 2866 """Return the reference mark that match the criteria. Either single 2867 position reference mark (text:reference-mark) or start of range 2868 reference (text:reference-mark-start). 2869 2870 Arguments: 2871 2872 position -- int 2873 2874 name -- str 2875 2876 Return: Element or None if not found 2877 """ 2878 if name: 2879 request = ( 2880 f"descendant::text:reference-mark-start" 2881 f'[@text:name="{name}"] ' 2882 f"| descendant::text:reference-mark" 2883 f'[@text:name="{name}"]' 2884 ) 2885 return self._filtered_element(request, position=0) 2886 request = ( 2887 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2888 ) 2889 return self._filtered_element(request, position)
Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).
Arguments:
position -- int
name -- str
Return: Element or None if not found
2891 def get_references(self, name: str | None = None) -> list[Element]: 2892 """Return all the references (text:reference-ref). If name is 2893 provided, returns the references of that name. 2894 2895 Return: list of Element 2896 2897 Arguments: 2898 2899 name -- str or None 2900 """ 2901 if name is None: 2902 return self._filtered_elements("descendant::text:reference-ref") 2903 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2904 return self._filtered_elements(request)
Return all the references (text:reference-ref). If name is provided, returns the references of that name.
Return: list of Element
Arguments:
name -- str or None
2910 def get_draw_groups( 2911 self, 2912 title: str | None = None, 2913 description: str | None = None, 2914 content: str | None = None, 2915 ) -> list[Element]: 2916 return self._filtered_elements( 2917 "descendant::draw:g", 2918 svg_title=title, 2919 svg_desc=description, 2920 content=content, 2921 )
2923 def get_draw_group( 2924 self, 2925 position: int = 0, 2926 name: str | None = None, 2927 title: str | None = None, 2928 description: str | None = None, 2929 content: str | None = None, 2930 ) -> Element | None: 2931 return self._filtered_element( 2932 "descendant::draw:g", 2933 position, 2934 draw_name=name, 2935 svg_title=title, 2936 svg_desc=description, 2937 content=content, 2938 )
2942 def get_draw_lines( 2943 self, 2944 draw_style: str | None = None, 2945 draw_text_style: str | None = None, 2946 content: str | None = None, 2947 ) -> list[Element]: 2948 """Return all the draw lines that match the criteria. 2949 2950 Arguments: 2951 2952 draw_style -- str 2953 2954 draw_text_style -- str 2955 2956 content -- str regex 2957 2958 Return: list of odf_shape 2959 """ 2960 return self._filtered_elements( 2961 "descendant::draw:line", 2962 draw_style=draw_style, 2963 draw_text_style=draw_text_style, 2964 content=content, 2965 )
Return all the draw lines that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
2967 def get_draw_line( 2968 self, 2969 position: int = 0, 2970 id: str | None = None, # noqa:A002 2971 content: str | None = None, 2972 ) -> Element | None: 2973 """Return the draw line that matches the criteria. 2974 2975 Arguments: 2976 2977 position -- int 2978 2979 id -- str 2980 2981 content -- str regex 2982 2983 Return: odf_shape or None if not found 2984 """ 2985 return self._filtered_element( 2986 "descendant::draw:line", position, draw_id=id, content=content 2987 )
Return the draw line that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
2991 def get_draw_rectangles( 2992 self, 2993 draw_style: str | None = None, 2994 draw_text_style: str | None = None, 2995 content: str | None = None, 2996 ) -> list[Element]: 2997 """Return all the draw rectangles that match the criteria. 2998 2999 Arguments: 3000 3001 draw_style -- str 3002 3003 draw_text_style -- str 3004 3005 content -- str regex 3006 3007 Return: list of odf_shape 3008 """ 3009 return self._filtered_elements( 3010 "descendant::draw:rect", 3011 draw_style=draw_style, 3012 draw_text_style=draw_text_style, 3013 content=content, 3014 )
Return all the draw rectangles that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3016 def get_draw_rectangle( 3017 self, 3018 position: int = 0, 3019 id: str | None = None, # noqa:A002 3020 content: str | None = None, 3021 ) -> Element | None: 3022 """Return the draw rectangle that matches the criteria. 3023 3024 Arguments: 3025 3026 position -- int 3027 3028 id -- str 3029 3030 content -- str regex 3031 3032 Return: odf_shape or None if not found 3033 """ 3034 return self._filtered_element( 3035 "descendant::draw:rect", position, draw_id=id, content=content 3036 )
Return the draw rectangle that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3040 def get_draw_ellipses( 3041 self, 3042 draw_style: str | None = None, 3043 draw_text_style: str | None = None, 3044 content: str | None = None, 3045 ) -> list[Element]: 3046 """Return all the draw ellipses that match the criteria. 3047 3048 Arguments: 3049 3050 draw_style -- str 3051 3052 draw_text_style -- str 3053 3054 content -- str regex 3055 3056 Return: list of odf_shape 3057 """ 3058 return self._filtered_elements( 3059 "descendant::draw:ellipse", 3060 draw_style=draw_style, 3061 draw_text_style=draw_text_style, 3062 content=content, 3063 )
Return all the draw ellipses that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3065 def get_draw_ellipse( 3066 self, 3067 position: int = 0, 3068 id: str | None = None, # noqa:A002 3069 content: str | None = None, 3070 ) -> Element | None: 3071 """Return the draw ellipse that matches the criteria. 3072 3073 Arguments: 3074 3075 position -- int 3076 3077 id -- str 3078 3079 content -- str regex 3080 3081 Return: odf_shape or None if not found 3082 """ 3083 return self._filtered_element( 3084 "descendant::draw:ellipse", position, draw_id=id, content=content 3085 )
Return the draw ellipse that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3089 def get_draw_connectors( 3090 self, 3091 draw_style: str | None = None, 3092 draw_text_style: str | None = None, 3093 content: str | None = None, 3094 ) -> list[Element]: 3095 """Return all the draw connectors that match the criteria. 3096 3097 Arguments: 3098 3099 draw_style -- str 3100 3101 draw_text_style -- str 3102 3103 content -- str regex 3104 3105 Return: list of odf_shape 3106 """ 3107 return self._filtered_elements( 3108 "descendant::draw:connector", 3109 draw_style=draw_style, 3110 draw_text_style=draw_text_style, 3111 content=content, 3112 )
Return all the draw connectors that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3114 def get_draw_connector( 3115 self, 3116 position: int = 0, 3117 id: str | None = None, # noqa:A002 3118 content: str | None = None, 3119 ) -> Element | None: 3120 """Return the draw connector that matches the criteria. 3121 3122 Arguments: 3123 3124 position -- int 3125 3126 id -- str 3127 3128 content -- str regex 3129 3130 Return: odf_shape or None if not found 3131 """ 3132 return self._filtered_element( 3133 "descendant::draw:connector", position, draw_id=id, content=content 3134 )
Return the draw connector that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3136 def get_orphan_draw_connectors(self) -> list[Element]: 3137 """Return a list of connectors which don't have any shape connected 3138 to them. 3139 """ 3140 connectors = [] 3141 for connector in self.get_draw_connectors(): 3142 start_shape = connector.get_attribute("draw:start-shape") 3143 end_shape = connector.get_attribute("draw:end-shape") 3144 if start_shape is None and end_shape is None: 3145 connectors.append(connector) 3146 return connectors
Return a list of connectors which don't have any shape connected to them.
3150 def get_tracked_changes(self) -> Element | None: 3151 """Return the tracked-changes part in the text body.""" 3152 return self.get_element("//text:tracked-changes")
Return the tracked-changes part in the text body.
3154 def get_changes_ids(self) -> list[Element | Text]: 3155 """Return a list of ids that refers to a change region in the tracked 3156 changes list. 3157 """ 3158 # Insertion changes 3159 xpath_query = "descendant::text:change-start/@text:change-id" 3160 # Deletion changes 3161 xpath_query += " | descendant::text:change/@text:change-id" 3162 return self.xpath(xpath_query)
Return a list of ids that refers to a change region in the tracked changes list.
3164 def get_text_change_deletions(self) -> list[Element]: 3165 """Return all the text changes of deletion kind: the tags text:change. 3166 Consider using : get_text_changes() 3167 3168 Return: list of Element 3169 """ 3170 return self._filtered_elements("descendant::text:text:change")
Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()
Return: list of Element
3172 def get_text_change_deletion( 3173 self, 3174 position: int = 0, 3175 idx: str | None = None, 3176 ) -> Element | None: 3177 """Return the text change of deletion kind that matches the criteria. 3178 Search only for the tags text:change. 3179 Consider using : get_text_change() 3180 3181 Arguments: 3182 3183 position -- int 3184 3185 idx -- str 3186 3187 Return: Element or None if not found 3188 """ 3189 return self._filtered_element( 3190 "descendant::text:change", position, change_id=idx 3191 )
Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3193 def get_text_change_starts(self) -> list[Element]: 3194 """Return all the text change-start. Search only for the tags 3195 text:change-start. 3196 Consider using : get_text_changes() 3197 3198 Return: list of Element 3199 """ 3200 return self._filtered_elements("descendant::text:change-start")
Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()
Return: list of Element
3202 def get_text_change_start( 3203 self, 3204 position: int = 0, 3205 idx: str | None = None, 3206 ) -> Element | None: 3207 """Return the text change-start that matches the criteria. Search 3208 only the tags text:change-start. 3209 Consider using : get_text_change() 3210 3211 Arguments: 3212 3213 position -- int 3214 3215 idx -- str 3216 3217 Return: Element or None if not found 3218 """ 3219 return self._filtered_element( 3220 "descendant::text:change-start", position, change_id=idx 3221 )
Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3223 def get_text_change_ends(self) -> list[Element]: 3224 """Return all the text change-end. Search only the tags 3225 text:change-end. 3226 Consider using : get_text_changes() 3227 3228 Return: list of Element 3229 """ 3230 return self._filtered_elements("descendant::text:change-end")
Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()
Return: list of Element
3232 def get_text_change_end( 3233 self, 3234 position: int = 0, 3235 idx: str | None = None, 3236 ) -> Element | None: 3237 """Return the text change-end that matches the criteria. Search only 3238 the tags text:change-end. 3239 Consider using : get_text_change() 3240 3241 Arguments: 3242 3243 position -- int 3244 3245 idx -- str 3246 3247 Return: Element or None if not found 3248 """ 3249 return self._filtered_element( 3250 "descendant::text:change-end", position, change_id=idx 3251 )
Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3253 def get_text_changes(self) -> list[Element]: 3254 """Return all the text changes, either single deletion 3255 (text:change) or start of range of changes (text:change-start). 3256 3257 Return: list of Element 3258 """ 3259 request = "descendant::text:change-start | descendant::text:change" 3260 return self._filtered_elements(request)
Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).
Return: list of Element
3262 def get_text_change( 3263 self, 3264 position: int = 0, 3265 idx: str | None = None, 3266 ) -> Element | None: 3267 """Return the text change that matches the criteria. Either single 3268 deletion (text:change) or start of range of changes (text:change-start). 3269 position : index of the element to retrieve if several matches, default 3270 is 0. 3271 idx : change-id of the element. 3272 3273 Arguments: 3274 3275 position -- int 3276 3277 idx -- str 3278 3279 Return: Element or None if not found 3280 """ 3281 if idx: 3282 request = ( 3283 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3284 f'| descendant::text:change[@text:change-id="{idx}"]' 3285 ) 3286 return self._filtered_element(request, 0) 3287 request = "descendant::text:change-start | descendant::text:change" 3288 return self._filtered_element(request, position)
Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3292 def get_tocs(self) -> list[Element]: 3293 """Return all the tables of contents. 3294 3295 Return: list of odf_toc 3296 """ 3297 return self._filtered_elements("text:table-of-content")
Return all the tables of contents.
Return: list of odf_toc
3299 def get_toc( 3300 self, 3301 position: int = 0, 3302 content: str | None = None, 3303 ) -> Element | None: 3304 """Return the table of contents that matches the criteria. 3305 3306 Arguments: 3307 3308 position -- int 3309 3310 content -- str regex 3311 3312 Return: odf_toc or None if not found 3313 """ 3314 return self._filtered_element( 3315 "text:table-of-content", position, content=content 3316 )
Return the table of contents that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: odf_toc or None if not found
3343 def get_style( 3344 self, 3345 family: str, 3346 name_or_element: str | Element | None = None, 3347 display_name: str | None = None, 3348 ) -> Element | None: 3349 """Return the style uniquely identified by the family/name pair. If 3350 the argument is already a style object, it will return it. 3351 3352 If the name is not the internal name but the name you gave in the 3353 desktop application, use display_name instead. 3354 3355 Arguments: 3356 3357 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3358 'number' 3359 3360 name_or_element -- str or Style 3361 3362 display_name -- str 3363 3364 Return: odf_style or None if not found 3365 """ 3366 if isinstance(name_or_element, Element): 3367 name = self.get_attribute("style:name") 3368 if name is not None: 3369 return name_or_element 3370 else: 3371 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3372 style_name = name_or_element 3373 is_default = not (style_name or display_name) 3374 tagname = self._get_style_tagname(family, is_default=is_default) 3375 # famattr became None if no "style:family" attribute 3376 if family: 3377 return self._filtered_element( 3378 tagname, 3379 0, 3380 style_name=style_name, 3381 display_name=display_name, 3382 family=family, 3383 ) 3384 else: 3385 return self._filtered_element( 3386 tagname, 3387 0, 3388 draw_name=style_name or display_name, 3389 family=family, 3390 )
Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: odf_style or None if not found
35class ElementTyped(Element): 36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text 128 129 def _get_typed_value( # noqa: C901 130 self, 131 value_type: str | None = None, 132 try_get_text: bool = True, 133 ) -> tuple[Any, str | None]: 134 """Return Python typed value. 135 136 Only for "with office:value-type" elements, not for meta fields.""" 137 value: Decimal | str | bool | None = None 138 if value_type is None: 139 read_value_type = self.get_attribute("office:value-type") 140 if isinstance(read_value_type, bool): 141 raise TypeError( 142 f'Wrong type for "office:value-type": {type(read_value_type)}' 143 ) 144 value_type = read_value_type 145 # value_type = to_str(value_type) 146 if value_type == "boolean": 147 value = self.get_attribute("office:boolean-value") 148 return (value, value_type) 149 if value_type in {"float", "percentage", "currency"}: 150 read_number = self.get_attribute("office:value") 151 if not isinstance(read_number, (Decimal, str)): 152 raise TypeError(f'Wrong type for "office:value": {type(read_number)}') 153 value = Decimal(read_number) 154 # Return 3 instead of 3.0 if possible 155 if int(value) == value: 156 return (int(value), value_type) 157 return (value, value_type) 158 if value_type == "date": 159 read_attribute = self.get_attribute("office:date-value") 160 if not isinstance(read_attribute, str): 161 raise TypeError( 162 f'Wrong type for "office:date-value": {type(read_attribute)}' 163 ) 164 if "T" in read_attribute: 165 return (DateTime.decode(read_attribute), value_type) 166 return (Date.decode(read_attribute), value_type) 167 if value_type == "string": 168 value = self.get_attribute("office:string-value") 169 if value is not None: 170 return (str(value), value_type) 171 if try_get_text: 172 list_value = [ 173 para.text_recursive for para in self.get_elements("text:p") 174 ] 175 if list_value: 176 return ("\n".join(list_value), value_type) 177 return (None, value_type) 178 if value_type == "time": 179 read_value = self.get_attribute("office:time-value") 180 if not isinstance(read_value, str): 181 raise TypeError( 182 f'Wrong type for "office:time-value": {type(read_value)}' 183 ) 184 time_value = Duration.decode(read_value) 185 return (time_value, value_type) 186 if value_type is None: 187 return (None, None) 188 raise ValueError(f'Unexpected value type: "{value_type}"') 189 190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text
190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Return Python typed value.
Only for "with office:value-type" elements, not for meta fields.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class EllipseShape(ShapeBase): 195 """Create a ellipse shape. 196 197 Arguments: 198 199 style -- str 200 201 text_style -- str 202 203 draw_id -- str 204 205 layer -- str 206 207 position -- (str, str) 208 209 size -- (str, str) 210 211 """ 212 213 _tag = "draw:ellipse" 214 _properties: tuple[PropDef, ...] = () 215 216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Create a ellipse shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix): 169 """ODF Frame "draw:frame" 170 171 Frames are not useful by themselves. You should consider calling 172 Frame.image_frame() or Frame.text_frame directly. 173 """ 174 175 _tag = "draw:frame" 176 _properties = ( 177 PropDef("name", "draw:name"), 178 PropDef("draw_id", "draw:id"), 179 PropDef("width", "svg:width"), 180 PropDef("height", "svg:height"), 181 PropDef("style", "draw:style-name"), 182 PropDef("pos_x", "svg:x"), 183 PropDef("pos_y", "svg:y"), 184 PropDef("presentation_class", "presentation:class"), 185 PropDef("layer", "draw:layer"), 186 PropDef("presentation_style", "presentation:style-name"), 187 ) 188 189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style 265 266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame 317 318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame 368 369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content 375 376 @text_content.setter 377 def text_content(self, text_or_element: Element | str) -> None: 378 text_box = self.get_element("draw:text-box") 379 if text_box is None: 380 text_box = Element.from_tag("draw:text-box") 381 self.append(text_box) 382 if isinstance(text_or_element, Element): 383 text_box.clear() 384 text_box.append(text_or_element) 385 else: 386 text_box.text_content = text_or_element 387 388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image") 396 397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image 414 415 def get_text_box(self) -> Element | None: 416 return self.get_element("draw:text-box") 417 418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box 439 440 @staticmethod 441 def _get_formatted_text_subresult(context: dict, element: Element) -> str: 442 str_list = [" "] 443 for child in element.children: 444 str_list.append(child.get_formatted_text(context)) 445 subresult = "".join(str_list) 446 subresult = subresult.replace("\n", "\n ") 447 return subresult.rstrip(" ") 448 449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
ODF Frame "draw:frame"
Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.
189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style
Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.
Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').
Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()
Arguments:
name -- str
draw_id -- str
style -- str
position -- (str, str)
size -- (str, str)
z_index -- int (default 0)
presentation_class -- str
anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
anchor_page -- int, page number is anchor_type is 'page'
layer -- str
presentation_style -- str
266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame
Create a ready-to-use image, since image must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
image -- DrawImage or str, DrawImage element or URL of the image
text -- str, text for the image
See Frame() initialization for the other arguments
Return: Frame
318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame
Create a ready-to-use text box, since text box must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
text_or_element -- str or Element, or list of them, text content
of the text box.
text_style -- str, name of the style for the text
See Frame() initialization for the other arguments
Return: Frame
369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image")
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image
418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box
449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.PosMix
- position
- odfdo.frame.ZMix
- z_index
- odfdo.frame.SizeMix
- size
33class Header(Paragraph): 34 """Specialised paragraph for headings "text:h".""" 35 36 _tag = "text:h" 37 _properties = ( 38 PropDef("level", "text:outline-level"), 39 PropDef("restart_numbering", "text:restart-numbering"), 40 PropDef("start_value", "text:start-value"), 41 PropDef("suppress_numbering", "text:suppress-numbering"), 42 ) 43 44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style 84 85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
Specialised paragraph for headings "text:h".
44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style
Create a header element of the given style and level, containing the optional given text.
Level count begins at 1.
Arguments:
level -- int
text -- str
restart_numbering -- bool
start_value -- int
style -- str
85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class IndexTitle(Element): 41 """The "text:index-title" element contains the title of an index. 42 43 The element has the following attributes: 44 text:name, text:protected, text:protection-key, 45 text:protection-key-digest-algorithm, text:style-name, xml:id. 46 47 The actual title is stored in a child element 48 """ 49 50 _tag = "text:index-title" 51 _properties = ( 52 PropDef("name", "text:name"), 53 PropDef("style", "text:style-name"), 54 PropDef("xml_id", "xml:id"), 55 PropDef("protected", "text:protected"), 56 PropDef("protection_key", "text:protection-key"), 57 PropDef( 58 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 59 ), 60 ) 61 62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style) 81 82 def set_title_text( 83 self, 84 title_text: str, 85 title_text_style: str | None = None, 86 ) -> None: 87 title = Paragraph(title_text, style=title_text_style) 88 self.append(title)
The "text:index-title" element contains the title of an index.
The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.
The actual title is stored in a child element
62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
447class IndexTitleTemplate(Element): 448 """ODF "text:index-title-template" 449 450 Arguments: 451 452 style -- str 453 """ 454 455 _tag = "text:index-title-template" 456 _properties = (PropDef("style", "text:style-name"),) 457 458 def __init__(self, style: str | None = None, **kwargs: Any) -> None: 459 super().__init__(**kwargs) 460 if self._do_init and style: 461 self.style = style
ODF "text:index-title-template"
Arguments:
style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
223class LineBreak(Element): 224 """This element represents a line break "text:line-break" """ 225 226 _tag = "text:line-break" 227 228 def __init__(self, **kwargs: Any) -> None: 229 super().__init__(**kwargs)
This element represents a line break "text:line-break"
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
89class LineShape(ShapeBase): 90 """Create a line shape. 91 92 Arguments: 93 94 style -- str 95 96 text_style -- str 97 98 draw_id -- str 99 100 layer -- str 101 102 p1 -- (str, str) 103 104 p2 -- (str, str) 105 """ 106 107 _tag = "draw:line" 108 _properties: tuple[PropDef, ...] = ( 109 PropDef("x1", "svg:x1"), 110 PropDef("y1", "svg:y1"), 111 PropDef("x2", "svg:x2"), 112 PropDef("y2", "svg:y2"), 113 ) 114 115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
Create a line shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
p1 -- (str, str)
p2 -- (str, str)
115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
33class Link(ParagraphBase): 34 """Link class, "text:a" ODF element.""" 35 36 _tag = "text:a" 37 _properties: tuple[PropDef, ...] = ( 38 PropDef("url", "xlink:href"), 39 PropDef("name", "office:name"), 40 PropDef("title", "office:title"), 41 PropDef("target_frame", "office:target-frame-name"), 42 PropDef("show", "xlink:show"), 43 PropDef("visited_style", "text:visited-style-name"), 44 PropDef("style", "text:style-name"), 45 ) 46 47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style 95 96 def __repr__(self) -> str: 97 return f"<{self.__class__.__name__} tag={self.tag} link={self.url}>" 98 99 def __str__(self) -> str: 100 if self.name: 101 return f"[{self.name}]({self.url})" 102 return f"({self.url})"
Link class, "text:a" ODF element.
47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style
Arguments:
url -- str
name -- str
title -- str
text -- str
target_frame -- '_self', '_blank', '_parent', '_top'
style -- str
visited_style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
68class List(Element): 69 """ODF List "text:list".""" 70 71 _tag = "text:list" 72 _properties = (PropDef("style", "text:style-name"),) 73 74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style 103 104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content) 116 117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position) 142 143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD) 160 161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined") 178 179 def append_item( 180 self, 181 item: ListItem | str | Element | None, 182 ) -> None: 183 if not isinstance(item, ListItem): 184 item = ListItem(item) 185 self.append(item) 186 187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
ODF List "text:list".
74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style
Create a list element, optionaly loading the list by a list of item (str or elements).
The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.
Arguments:
list_content -- str or Element, or a list of str or Element
style -- str
104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content)
Return all the list items that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position)
Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD)
161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined")
187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
41class ListItem(Element): 42 """ODF element "text:list-item", item of a List.""" 43 44 _tag = "text:list-item" 45 46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
ODF element "text:list-item", item of a List.
46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
Create a list item element, optionaly passing at creation time a string or Element as content.
Arguments:
text_or_element -- str or ODF Element
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Manifest(XmlPart): 32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query) 39 40 def _file_entry(self, full_path: str) -> Element: 41 xpath_query = ( 42 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 43 ) 44 result = self.xpath(xpath_query) 45 if not result: 46 raise KeyError(f"Path not found: '{full_path}'") 47 return result[0] # type: ignore 48 49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result 66 67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0]) 80 81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type) 92 93 @staticmethod 94 def make_file_entry(full_path: str, media_type: str) -> Element: 95 tag = ( 96 f"<manifest:file-entry " 97 f'manifest:media-type="{media_type}" ' 98 f'manifest:full-path="{full_path}"/>' 99 ) 100 return Element.from_tag(tag) 101 102 def add_full_path(self, full_path: str, media_type: str = "") -> None: 103 # Existing? 104 existing = self.get_media_type(full_path) 105 if existing is not None: 106 self.set_media_type(full_path, media_type) 107 root = self.root 108 root.append(self.make_file_entry(full_path, media_type)) 109 110 def del_full_path(self, full_path: str) -> None: 111 file_entry = self._file_entry(full_path) 112 self.root.delete(file_entry)
Representation of an XML part.
Abstraction of the XML library behind.
32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query)
Return the list of full paths in the manifest.
Return: list of str
49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result
Return the list of (full_path, media_type) pairs in the manifest.
Return: list of str tuples
67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0])
Get the media type of an existing path.
Return: str
81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type)
Set the media type of an existing path.
Arguments:
full_path -- str
media_type -- str
Inherited Members
43class Meta(XmlPart): 44 def __init__(self, *args: Any, **kwargs: Any) -> None: 45 super().__init__(*args, **kwargs) 46 self._generator_modified: bool = False 47 48 def get_meta_body(self) -> Element: 49 return self.get_element("//office:meta") 50 51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text 62 63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title 77 78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text 87 88 # As named in OOo 89 get_comments = get_description 90 91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description 103 104 set_comments = set_description 105 106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text 115 116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject 128 129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text 143 144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language 165 166 @staticmethod 167 def _is_RFC3066(lang: str) -> bool: 168 def test_part1(part1: str) -> bool: 169 if not 2 <= len(part1) <= 3: 170 return False 171 return all(x in ascii_letters for x in part1) 172 173 def test_part2(part2: str) -> bool: 174 return all(x in ascii_letters or x in digits for x in part2) 175 176 if not lang or not isinstance(lang, str): 177 return False 178 if "-" not in lang: 179 return test_part1(lang) 180 parts = lang.split("-") 181 if len(parts) > 3: 182 return False 183 if not test_part1(parts[0]): 184 return False 185 return all(test_part2(p) for p in parts[1:]) 186 187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date) 197 198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date) 210 211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date) 221 222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date) 234 235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text 249 250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator 266 267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text 281 282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator 298 299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text 309 310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords 323 324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration) 335 336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration) 350 351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles) 362 363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles) 379 380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text 394 395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True 412 413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR) 423 424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic 447 448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue)) 476 477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result 493 494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result 520 521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value 553 554 def _get_meta_value( 555 self, element: Element, full: bool = False 556 ) -> Any | tuple[Any, str, str]: 557 """get_value() deicated to the meta data part, for one meta element.""" 558 if full: 559 return self._get_meta_value_full(element) 560 else: 561 return self._get_meta_value_full(element)[0] 562 563 @staticmethod 564 def _get_meta_value_full(element: Element) -> tuple[Any, str, str]: 565 """get_value deicated to the meta data part, for one meta element.""" 566 # name = element.get_attribute('meta:name') 567 value_type = element.get_attribute_string("meta:value-type") 568 if value_type is None: 569 value_type = "string" 570 text = element.text 571 # Interpretation 572 if value_type == "boolean": 573 return (Boolean.decode(text), value_type, text) 574 if value_type in ("float", "percentage", "currency"): 575 return (Decimal(text), value_type, text) 576 if value_type == "date": 577 if "T" in text: 578 return (DateTime.decode(text), value_type, text) 579 else: 580 return (Date.decode(text), value_type, text) 581 if value_type == "string": 582 return (text, value_type, text) 583 if value_type == "time": 584 return (Duration.decode(text), value_type, text) 585 raise TypeError(f"Unknown value type: '{value_type!r}'")
Representation of an XML part.
Abstraction of the XML library behind.
51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text
Get the title of the document.
This is not the first heading but the title metadata.
Return: str (or None if inexistant)
63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title
Set the title of the document.
This is not the first heading but the title metadata.
Arguments:
title -- str
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text
Get the subject of the document.
Return: str (or None if inexistant)
116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject
Set the subject of the document.
Arguments:
subject -- str
129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text
Get the language code of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_language()
fr-FR
144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language
Set the language code of the document.
Arguments:
language -- str
Example::
>>> document.meta.set_language('fr-FR')
187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date)
Get the last modified date of the document.
Return: datetime (or None if inexistant)
198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date)
Set the last modified date of the document.
Arguments:
date -- datetime
211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date)
Get the creation date of the document.
Return: datetime (or None if inexistant)
222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date)
Set the creation date of the document.
Arguments:
date -- datetime
235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text
Get the first creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_initial_creator()
Unknown
250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator
Set the first creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_initial_creator("Plato")
267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text
Get the creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_creator()
Unknown
282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator
Set the creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_creator("Plato")
299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text
Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.
Return: str (or None if inexistant)
310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords
Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.
Arguments:
keywords -- str
324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration)
Get the time the document was edited, as reported by the generator.
Return: timedelta (or None if inexistant)
336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration)
Set the time the document was edited.
Arguments:
duration -- timedelta
351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles)
Get the number of times the document was edited, as reported by the generator.
Return: int (or None if inexistant)
363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles)
Set the number of times the document was edited.
Arguments:
cycles -- int
380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text
Get the signature of the software that generated this document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_generator()
KOffice/2.0.0
395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True
Set the signature of the software that generated this document.
Arguments:
generator -- str
Example::
>>> document.meta.set_generator("Odfdo experiment")
413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR)
Set the signature of the software that generated this document to ourself.
Example::
>>> document.meta.set_generator_default()
424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic
Get the statistic from the software that generated this document.
Return: dict (or None if inexistant)
Example::
>>> document.get_statistic():
{'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue))
Set the statistic for the documents: number of words, paragraphs, etc.
Arguments:
statistic -- dict
Example::
>>> statistic = {'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result
Return a dict of str/value mapping.
Value types can be: Decimal, date, time, boolean or str.
494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result
Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.
Arguments:
name -- string, name (meta:name content)
521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value
Inherited Members
2844class NamedRange(Element): 2845 """ODF Named Range "table:named-range". Identifies inside the spreadsheet 2846 a range of cells of a table by a name and the name of the table. 2847 2848 Name Ranges have the following attributes: 2849 2850 name -- name of the named range 2851 2852 table_name -- name of the table 2853 2854 start -- first cell of the named range, tuple (x, y) 2855 2856 end -- last cell of the named range, tuple (x, y) 2857 2858 crange -- range of the named range, tuple (x, y, z, t) 2859 2860 usage -- None or str, usage of the named range. 2861 """ 2862 2863 _tag = "table:named-range" 2864 2865 def __init__( 2866 self, 2867 name: str | None = None, 2868 crange: str | tuple | list | None = None, 2869 table_name: str | None = None, 2870 usage: str | None = None, 2871 **kwargs: Any, 2872 ) -> None: 2873 """Create a Named Range element. 'name' must contains only letters, digits 2874 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2875 a correct table name (no "'" or "/" in it). 2876 2877 Arguments: 2878 2879 name -- str, name of the named range 2880 2881 crange -- str or tuple of int, cell or area coordinate 2882 2883 table_name -- str, name of the table 2884 2885 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2886 """ 2887 super().__init__(**kwargs) 2888 self.usage = None 2889 if self._do_init: 2890 self.name = name or "" 2891 self.table_name = _table_name_check(table_name) 2892 self.set_range(crange or "") 2893 self.set_usage(usage) 2894 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2895 if not cell_range_address: 2896 self.table_name = "" 2897 self.start = None 2898 self.end = None 2899 self.crange = None 2900 self.usage = None 2901 return 2902 self.usage = self.get_attribute("table:range-usable-as") 2903 name_range = cell_range_address.replace("$", "") 2904 name, crange = name_range.split(".", 1) 2905 if name.startswith("'") and name.endswith("'"): 2906 name = name[1:-1] 2907 self.table_name = name 2908 crange = crange.replace(".", "") 2909 self._set_range(crange) 2910 2911 def set_usage(self, usage: str | None = None) -> None: 2912 """Set the usage of the Named Range. Usage can be None (default) or one 2913 of : 2914 'print-range' 2915 'filter' 2916 'repeat-column' 2917 'repeat-row' 2918 2919 Arguments: 2920 2921 usage -- None or str 2922 """ 2923 if usage is not None: 2924 usage = usage.strip().lower() 2925 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2926 usage = None 2927 if usage is None: 2928 with contextlib.suppress(KeyError): 2929 self.del_attribute("table:range-usable-as") 2930 self.usage = None 2931 else: 2932 self.set_attribute("table:range-usable-as", usage) 2933 self.usage = usage 2934 2935 @property 2936 def name(self) -> str | None: 2937 """Get / set the name of the table.""" 2938 return self.get_attribute_string("table:name") 2939 2940 @name.setter 2941 def name(self, name: str) -> None: 2942 """Set the name of the Named Range. The name is mandatory, if a Named 2943 Range of the same name exists, it will be replaced. Name must contains 2944 only alphanumerics characters and '_', and can not be of a cell 2945 coordinates form like 'AB12'. 2946 2947 Arguments: 2948 2949 name -- str 2950 """ 2951 name = name.strip() 2952 if not name: 2953 raise ValueError("Name required.") 2954 for x in name: 2955 if x in forbidden_in_named_range(): 2956 raise ValueError(f"Character forbidden '{x}' ") 2957 step = "" 2958 for x in name: 2959 if x in string.ascii_letters and step in ("", "A"): 2960 step = "A" 2961 continue 2962 elif step in ("A", "A1") and x in string.digits: 2963 step = "A1" 2964 continue 2965 else: 2966 step = "" 2967 break 2968 if step == "A1": 2969 raise ValueError("Name of the type 'ABC123' is not allowed.") 2970 with contextlib.suppress(Exception): 2971 # we are not on an inserted in a document. 2972 body = self.document_body 2973 named_range = body.get_named_range(name) # type: ignore 2974 if named_range: 2975 named_range.delete() 2976 self.set_attribute("table:name", name) 2977 2978 def set_table_name(self, name: str) -> None: 2979 """Set the name of the table of the Named Range. The name is mandatory. 2980 2981 Arguments: 2982 2983 name -- str 2984 """ 2985 self.table_name = _table_name_check(name) 2986 self._update_attributes() 2987 2988 def _set_range(self, coord: tuple | list | str) -> None: 2989 digits = convert_coordinates(coord) 2990 if len(digits) == 4: 2991 x, y, z, t = digits 2992 else: 2993 x, y = digits 2994 z, t = digits 2995 self.start = x, y # type: ignore 2996 self.end = z, t # type: ignore 2997 self.crange = x, y, z, t # type: ignore 2998 2999 def set_range(self, crange: str | tuple | list) -> None: 3000 """Set the range of the named range. Range can be either one cell 3001 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3002 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3003 3004 Arguments: 3005 3006 crange -- str or tuple of int, cell or area coordinate 3007 """ 3008 self._set_range(crange) 3009 self._update_attributes() 3010 3011 def _update_attributes(self) -> None: 3012 self.set_attribute("table:base-cell-address", self._make_base_cell_address()) 3013 self.set_attribute("table:cell-range-address", self._make_cell_range_address()) 3014 3015 def _make_base_cell_address(self) -> str: 3016 # assuming we got table_name and range 3017 if " " in self.table_name: 3018 name = f"'{self.table_name}'" 3019 else: 3020 name = self.table_name 3021 return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}" # type: ignore 3022 3023 def _make_cell_range_address(self) -> str: 3024 # assuming we got table_name and range 3025 if " " in self.table_name: 3026 name = f"'{self.table_name}'" 3027 else: 3028 name = self.table_name 3029 if self.start == self.end: 3030 return self._make_base_cell_address() 3031 return ( 3032 f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:" # type: ignore 3033 f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}" # type: ignore 3034 ) 3035 3036 def get_values( 3037 self, 3038 cell_type: str | None = None, 3039 complete: bool = True, 3040 get_type: bool = False, 3041 flat: bool = False, 3042 ) -> list: 3043 """Shortcut to retrieve the values of the cells of the named range. See 3044 table.get_values() for the arguments description and return format. 3045 """ 3046 body = self.document_body 3047 if not body: 3048 raise ValueError("Table is not inside a document.") 3049 table = body.get_table(name=self.table_name) 3050 if table is None: 3051 raise ValueError 3052 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore 3053 3054 def get_value(self, get_type: bool = False) -> Any: 3055 """Shortcut to retrieve the value of the first cell of the named range. 3056 See table.get_value() for the arguments description and return format. 3057 """ 3058 body = self.document_body 3059 if not body: 3060 raise ValueError("Table is not inside a document.") 3061 table = body.get_table(name=self.table_name) 3062 if table is None: 3063 raise ValueError 3064 return table.get_value(self.start, get_type) # type: ignore 3065 3066 def set_values( 3067 self, 3068 values: list, 3069 style: str | None = None, 3070 cell_type: str | None = None, 3071 currency: str | None = None, 3072 ) -> None: 3073 """Shortcut to set the values of the cells of the named range. 3074 See table.set_values() for the arguments description. 3075 """ 3076 body = self.document_body 3077 if not body: 3078 raise ValueError("Table is not inside a document.") 3079 table = body.get_table(name=self.table_name) 3080 if table is None: 3081 raise ValueError 3082 table.set_values( # type: ignore 3083 values, 3084 coord=self.crange, 3085 style=style, 3086 cell_type=cell_type, 3087 currency=currency, 3088 ) 3089 3090 def set_value( 3091 self, 3092 value: Any, 3093 cell_type: str | None = None, 3094 currency: str | None = None, 3095 style: str | None = None, 3096 ) -> None: 3097 """Shortcut to set the value of the first cell of the named range. 3098 See table.set_value() for the arguments description. 3099 """ 3100 body = self.document_body 3101 if not body: 3102 raise ValueError("Table is not inside a document.") 3103 table = body.get_table(name=self.table_name) 3104 if table is None: 3105 raise ValueError 3106 table.set_value( # type: ignore 3107 coord=self.start, 3108 value=value, 3109 cell_type=cell_type, 3110 currency=currency, 3111 style=style, 3112 )
ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.
Name Ranges have the following attributes:
name -- name of the named range
table_name -- name of the table
start -- first cell of the named range, tuple (x, y)
end -- last cell of the named range, tuple (x, y)
crange -- range of the named range, tuple (x, y, z, t)
usage -- None or str, usage of the named range.
2865 def __init__( 2866 self, 2867 name: str | None = None, 2868 crange: str | tuple | list | None = None, 2869 table_name: str | None = None, 2870 usage: str | None = None, 2871 **kwargs: Any, 2872 ) -> None: 2873 """Create a Named Range element. 'name' must contains only letters, digits 2874 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2875 a correct table name (no "'" or "/" in it). 2876 2877 Arguments: 2878 2879 name -- str, name of the named range 2880 2881 crange -- str or tuple of int, cell or area coordinate 2882 2883 table_name -- str, name of the table 2884 2885 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2886 """ 2887 super().__init__(**kwargs) 2888 self.usage = None 2889 if self._do_init: 2890 self.name = name or "" 2891 self.table_name = _table_name_check(table_name) 2892 self.set_range(crange or "") 2893 self.set_usage(usage) 2894 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2895 if not cell_range_address: 2896 self.table_name = "" 2897 self.start = None 2898 self.end = None 2899 self.crange = None 2900 self.usage = None 2901 return 2902 self.usage = self.get_attribute("table:range-usable-as") 2903 name_range = cell_range_address.replace("$", "") 2904 name, crange = name_range.split(".", 1) 2905 if name.startswith("'") and name.endswith("'"): 2906 name = name[1:-1] 2907 self.table_name = name 2908 crange = crange.replace(".", "") 2909 self._set_range(crange)
Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2911 def set_usage(self, usage: str | None = None) -> None: 2912 """Set the usage of the Named Range. Usage can be None (default) or one 2913 of : 2914 'print-range' 2915 'filter' 2916 'repeat-column' 2917 'repeat-row' 2918 2919 Arguments: 2920 2921 usage -- None or str 2922 """ 2923 if usage is not None: 2924 usage = usage.strip().lower() 2925 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2926 usage = None 2927 if usage is None: 2928 with contextlib.suppress(KeyError): 2929 self.del_attribute("table:range-usable-as") 2930 self.usage = None 2931 else: 2932 self.set_attribute("table:range-usable-as", usage) 2933 self.usage = usage
Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'
Arguments:
usage -- None or str
2935 @property 2936 def name(self) -> str | None: 2937 """Get / set the name of the table.""" 2938 return self.get_attribute_string("table:name")
Get / set the name of the table.
2978 def set_table_name(self, name: str) -> None: 2979 """Set the name of the table of the Named Range. The name is mandatory. 2980 2981 Arguments: 2982 2983 name -- str 2984 """ 2985 self.table_name = _table_name_check(name) 2986 self._update_attributes()
Set the name of the table of the Named Range. The name is mandatory.
Arguments:
name -- str
2999 def set_range(self, crange: str | tuple | list) -> None: 3000 """Set the range of the named range. Range can be either one cell 3001 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3002 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3003 3004 Arguments: 3005 3006 crange -- str or tuple of int, cell or area coordinate 3007 """ 3008 self._set_range(crange) 3009 self._update_attributes()
Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
crange -- str or tuple of int, cell or area coordinate
3036 def get_values( 3037 self, 3038 cell_type: str | None = None, 3039 complete: bool = True, 3040 get_type: bool = False, 3041 flat: bool = False, 3042 ) -> list: 3043 """Shortcut to retrieve the values of the cells of the named range. See 3044 table.get_values() for the arguments description and return format. 3045 """ 3046 body = self.document_body 3047 if not body: 3048 raise ValueError("Table is not inside a document.") 3049 table = body.get_table(name=self.table_name) 3050 if table is None: 3051 raise ValueError 3052 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore
Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.
3054 def get_value(self, get_type: bool = False) -> Any: 3055 """Shortcut to retrieve the value of the first cell of the named range. 3056 See table.get_value() for the arguments description and return format. 3057 """ 3058 body = self.document_body 3059 if not body: 3060 raise ValueError("Table is not inside a document.") 3061 table = body.get_table(name=self.table_name) 3062 if table is None: 3063 raise ValueError 3064 return table.get_value(self.start, get_type) # type: ignore
Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.
3066 def set_values( 3067 self, 3068 values: list, 3069 style: str | None = None, 3070 cell_type: str | None = None, 3071 currency: str | None = None, 3072 ) -> None: 3073 """Shortcut to set the values of the cells of the named range. 3074 See table.set_values() for the arguments description. 3075 """ 3076 body = self.document_body 3077 if not body: 3078 raise ValueError("Table is not inside a document.") 3079 table = body.get_table(name=self.table_name) 3080 if table is None: 3081 raise ValueError 3082 table.set_values( # type: ignore 3083 values, 3084 coord=self.crange, 3085 style=style, 3086 cell_type=cell_type, 3087 currency=currency, 3088 )
Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.
3090 def set_value( 3091 self, 3092 value: Any, 3093 cell_type: str | None = None, 3094 currency: str | None = None, 3095 style: str | None = None, 3096 ) -> None: 3097 """Shortcut to set the value of the first cell of the named range. 3098 See table.set_value() for the arguments description. 3099 """ 3100 body = self.document_body 3101 if not body: 3102 raise ValueError("Table is not inside a document.") 3103 table = body.get_table(name=self.table_name) 3104 if table is None: 3105 raise ValueError 3106 table.set_value( # type: ignore 3107 coord=self.start, 3108 value=value, 3109 cell_type=cell_type, 3110 currency=currency, 3111 style=style, 3112 )
Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
57class Note(Element): 58 """Either a footnote or a endnote element with the given text, 59 optionally referencing it using the given note_id. 60 61 Arguments: 62 63 note_class -- 'footnote' or 'endnote' 64 65 note_id -- str 66 67 citation -- str 68 69 body -- str or Element 70 """ 71 72 _tag = "text:note" 73 _properties = ( 74 PropDef("note_class", "text:note-class"), 75 PropDef("note_id", "text:id"), 76 ) 77 78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body 97 98 @property 99 def citation(self) -> str: 100 note_citation = self.get_element("text:note-citation") 101 if note_citation: 102 return note_citation.text 103 return "" 104 105 @citation.setter 106 def citation(self, text: str | None) -> None: 107 note_citation = self.get_element("text:note-citation") 108 if note_citation: 109 note_citation.text = text # type:ignore 110 111 @property 112 def note_body(self) -> str: 113 note_body = self.get_element("text:note-body") 114 if note_body: 115 return note_body.text_content 116 return "" 117 118 @note_body.setter 119 def note_body(self, text_or_element: Element | str | None) -> None: 120 note_body = self.get_element("text:note-body") 121 if not note_body: 122 return None 123 if text_or_element is None: 124 note_body.text_content = "" 125 elif isinstance(text_or_element, str): 126 note_body.text_content = text_or_element 127 elif isinstance(text_or_element, Element): 128 note_body.clear() 129 note_body.append(text_or_element) 130 else: 131 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 132 133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.
Arguments:
note_class -- 'footnote' or 'endnote'
note_id -- str
citation -- str
body -- str or Element
78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body
133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
831def PageBreak() -> Paragraph: 832 """Return an empty paragraph with a manual page break. 833 834 Using this function requires to register the page break style with: 835 document.add_page_break_style() 836 """ 837 return Paragraph("", style="odfdopagebreak")
Return an empty paragraph with a manual page break.
Using this function requires to register the page break style with: document.add_page_break_style()
147class Paragraph(ParagraphBase): 148 """Specialised element for paragraphs "text:p". The "text:p" element 149 represents a paragraph, which is the basic unit of text in an OpenDocument 150 file. 151 """ 152 153 _tag = "text:p" 154 155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style 178 179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD) 209 210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element 332 333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag 377 378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference 478 479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag 522 523 def insert_variable(self, variable_element: Element, after: str | None) -> None: 524 self._insert(variable_element, after=after, main_text=True) 525 526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span 556 557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect) 568 569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans) 577 578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link 608 609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip) 613 614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links) 623 624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 ) 688 689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.
155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style
Create a paragraph element of the given style containing the optional given text.
Arguments:
text -- str or Element
style -- str
179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD)
210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element
Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.
If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).
Arguments:
annotation_element -- Annotation or None
before -- str regular expression or None
after -- str regular expression or Element or None
content -- str regular expression or None, or Element
position -- int or tuple of int
body -- str or Element
creator -- str
date -- datetime
333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag
Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
annotation_element -- Annotation (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference
Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.
If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
Name is mandatory and shall be unique in the document for the preference mark range.
Arguments:
name -- str
before -- str regular expression or None
after -- str regular expression or None,
content -- str regular expression or None, or Element
position -- int or tuple of int
Return: the created ReferenceMark or ReferenceMarkStart
479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag
Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span
set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
style -- str
regex -- str regular expression
offset -- int
length -- int
557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect)
Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.
569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans)
Send back a copy of the element, the spans (not a clone) removed.
Arguments:
spans -- Element or list of Element
578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link
set_link(url, regex=None, offset=None, length=0) Make a link to the provided url from text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
url -- str
regex -- str regular expression
offset -- int
length -- int
609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip)
Send back a copy of the element, without links tags.
614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links)
Send back a copy of the element (not a clone), with the sub links removed.
Arguments:
links -- Link or list of Link
624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 )
Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.
name is mandatory and should represent an existing reference mark of the document.
ref_format is the argument for format reference (default is 'page').
The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.
If after is an ODF Element, the reference is inserted as first child of this element.
Arguments:
name -- str
ref_format -- one of : 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value',
'number', 'number-all-superior',
'number-no-superior'
before -- str regular expression or None
after -- str regular expression or odf element or None
position -- int
display -- str or None
689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.
So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.
If content is not None these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", content="xyz")
and:
paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")
If position is a 2-tuple, these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", position=(10, 20))
and:
paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")
Arguments:
name -- str
before -- str regex
after -- str regex
position -- int or (int, int)
role -- None, "start" or "end"
content -- str regex
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
146class RectangleShape(ShapeBase): 147 """Create a rectangle shape. 148 149 Arguments: 150 151 style -- str 152 153 text_style -- str 154 155 draw_id -- str 156 157 layer -- str 158 159 position -- (str, str) 160 161 size -- (str, str) 162 163 """ 164 165 _tag = "draw:rect" 166 _properties: tuple[PropDef, ...] = () 167 168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Create a rectangle shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
59class Reference(Element): 60 """A reference to a content marked by a reference mark. 61 The odf_reference element ("text:reference-ref") represents a field that 62 references a "text:reference-mark-start" or "text:reference-mark" element. 63 Its text:reference-format attribute specifies what is displayed from the 64 referenced element. Default is 'page' 65 Actual content is not updated except for the 'text' format by the 66 update() method. 67 68 69 Creation of references can be tricky, consider using this method: 70 odfdo.paragraph.insert_reference() 71 72 Values for text:reference-format : 73 The defined values for the text:reference-format attribute supported by 74 all reference fields are: 75 - 'chapter': displays the number of the chapter in which the 76 referenced item appears. 77 - 'direction': displays whether the referenced item is above or 78 below the reference field. 79 - 'page': displays the number of the page on which the referenced 80 item appears. 81 - 'text': displays the text of the referenced item. 82 Additional defined values for the text:reference-format attribute 83 supported by references to sequence fields are: 84 - 'caption': displays the caption in which the sequence is used. 85 - 'category-and-value': displays the name and value of the sequence. 86 - 'value': displays the value of the sequence. 87 88 References to bookmarks and other references support additional values, 89 which display the list label of the referenced item. If the referenced 90 item is contained in a list or a numbered paragraph, the list label is 91 the formatted number of the paragraph which contains the referenced 92 item. If the referenced item is not contained in a list or numbered 93 paragraph, the list label is empty, and the referenced field therefore 94 displays nothing. If the referenced bookmark or reference contains more 95 than one paragraph, the list label of the paragraph at which the 96 bookmark or reference starts is taken. 97 98 Additional defined values for the text:reference-format attribute 99 supported by all references to bookmark's or other reference fields 100 are: 101 - 'number': displays the list label of the referenced item. [...] 102 - 'number-all-superior': displays the list label of the referenced 103 item and adds the contents of all list labels of superior levels 104 in front of it. [...] 105 - 'number-no-superior': displays the contents of the list label of 106 the referenced item. 107 """ 108 109 _tag = "text:reference-ref" 110 _properties = (PropDef("name", "text:ref-name"),) 111 format_allowed = ( 112 "chapter", 113 "direction", 114 "page", 115 "text", 116 "caption", 117 "category-and-value", 118 "value", 119 "number", 120 "number-all-superior", 121 "number-no-superior", 122 ) 123 124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format 149 150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None 156 157 @ref_format.setter 158 def ref_format(self, ref_format: str) -> None: 159 """Set the text:reference-format attribute. 160 161 Arguments: 162 163 ref_format -- str 164 """ 165 if not ref_format or ref_format not in self.format_allowed: 166 ref_format = "page" 167 self.set_attribute("text:reference-format", ref_format) 168 169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.
Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()
Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.
References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.
Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
- 'number': displays the list label of the referenced item. [...]
- 'number-all-superior': displays the list label of the referenced
item and adds the contents of all list labels of superior levels
in front of it. [...]
- 'number-no-superior': displays the contents of the list label of
the referenced item.
124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format
Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.
Consider using: odfdo.paragraph.insert_reference()
The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.
Arguments:
name -- str : name of the reference mark
ref_format -- str : format of the field. Default is 'page', allowed
values are 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value', 'number',
'number-all-superior', 'number-no-superior'.
150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None
Set the text:reference-format attribute.
Arguments:
ref_format -- str
169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
192class ReferenceMark(Element): 193 """A point reference. 194 A point reference marks a position in text and is represented by a single 195 "text:reference-mark" element. 196 """ 197 198 _tag = "text:reference-mark" 199 _properties = (PropDef("name", "text:name"),) 200 201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.
201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
218class ReferenceMarkEnd(Element): 219 """The "text:reference-mark-end" element represents the end of a range 220 reference. 221 """ 222 223 _tag = "text:reference-mark-end" 224 _properties = (PropDef("name", "text:name"),) 225 226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name 239 240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
The "text:reference-mark-end" element represents the end of a range reference.
226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name
The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()
Arguments:
name -- str
240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
Return the text between reference-mark-start and reference-mark-end.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
255class ReferenceMarkStart(Element): 256 """The "text:reference-mark-start" element represents the start of a 257 range reference. 258 """ 259 260 _tag = "text:reference-mark-start" 261 _properties = (PropDef("name", "text:name"),) 262 263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name 275 276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result 286 287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list) 329 330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
The "text:reference-mark-start" element represents the start of a range reference.
263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name
The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result
Return the text between reference-mark-start and reference-mark-end.
287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.
Arguments:
no_header -- boolean (default to False), translate existing headers
tags "text:h" into paragraphs "text:p".
clean -- boolean (default to True), suppress unwanted tags. Striped
tags are : 'text:change', 'text:change-start',
'text:change-end', 'text:reference-mark',
'text:reference-mark-start', 'text:reference-mark-end'.
as_xml -- boolean (default to False), format the returned content as
a XML string (serialization).
as_list -- boolean (default to False), do not embed the returned
content in a "office:text'" element, instead simply
return a raw list of odf elements.
330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For odf_reference_mark_start : delete the reference-end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class Row(Element): 53 """ODF table row "table:table-row" """ 54 55 _tag = "table:table-row" 56 _caching = True 57 _append = Element.append 58 59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache() 99 100 def _get_cells(self) -> list[Element]: 101 return self.get_elements(_xpath_cell) 102 103 def _translate_row_coordinates( 104 self, 105 coord: tuple | list | str, 106 ) -> tuple[int | None, int | None]: 107 xyzt = convert_coordinates(coord) 108 if len(xyzt) == 2: 109 x, z = xyzt 110 else: 111 x, _, z, __ = xyzt 112 if x and x < 0: 113 x = increment(x, self.width) 114 if z and z < 0: 115 z = increment(z, self.width) 116 return (x, z) 117 118 def _compute_row_cache(self) -> None: 119 idx_repeated_seq = self.elements_repeated_sequence( 120 _xpath_cell, "table:number-columns-repeated" 121 ) 122 self._rmap = make_cache_map(idx_repeated_seq) 123 124 # Public API 125 126 @property 127 def clone(self) -> Row: 128 clone = Element.clone.fget(self) # type: ignore 129 clone.y = self.y 130 if hasattr(self, "_tmap"): 131 if hasattr(self, "_rmap"): 132 clone._rmap = self._rmap[:] 133 clone._tmap = self._tmap[:] 134 clone._cmap = self._cmap[:] 135 return clone 136 137 def _set_repeated(self, repeated: int | None) -> None: 138 """Internal only. Set the numnber of times the row is repeated, or 139 None to delete it. Without changing cache. 140 141 Arguments: 142 143 repeated -- int 144 """ 145 if repeated is None or repeated < 2: 146 with contextlib.suppress(KeyError): 147 self.del_attribute("table:number-rows-repeated") 148 return 149 self.set_attribute("table:number-rows-repeated", str(repeated)) 150 151 @property 152 def repeated(self) -> int | None: 153 """Get / set the number of times the row is repeated. 154 155 Always None when using the table API. 156 157 Return: int or None 158 """ 159 repeated = self.get_attribute("table:number-rows-repeated") 160 if repeated is None: 161 return None 162 return int(repeated) 163 164 @repeated.setter 165 def repeated(self, repeated: int | None) -> None: 166 self._set_repeated(repeated) 167 # update cache 168 current: Element = self 169 while True: 170 # look for Table, parent may be group of rows 171 upper = current.parent 172 if not upper: 173 # lonely row 174 return 175 # parent may be group of rows, not table 176 if isinstance(upper, Element) and upper._tag == "table:table": 177 break 178 current = upper 179 # fixme : need to optimize this 180 if isinstance(upper, Element) and upper._tag == "table:table": 181 upper._compute_table_cache() 182 if hasattr(self, "_tmap"): 183 del self._tmap[:] 184 self._tmap.extend(upper._tmap) 185 else: 186 self._tmap = upper._tmap 187 188 @property 189 def style(self) -> str | None: 190 """Get /set the style of the row itself. 191 192 Return: str 193 """ 194 return self.get_attribute("table:style-name") # type: ignore 195 196 @style.setter 197 def style(self, style: str | Element) -> None: 198 self.set_style_attribute("table:style-name", style) 199 200 @property 201 def width(self) -> int: 202 """Get the number of expected cells in the row, i.e. addition 203 repetitions. 204 205 Return: int 206 """ 207 try: 208 value = self._rmap[-1] + 1 209 except Exception: 210 value = 0 211 return value 212 213 def _translate_x_from_any(self, x: str | int) -> int: 214 return translate_from_any(x, self.width, 0) 215 216 def traverse( # noqa: C901 217 self, 218 start: int | None = None, 219 end: int | None = None, 220 ) -> Iterator[Cell]: 221 """Yield as many cell elements as expected cells in the row, i.e. 222 expand repetitions by returning the same cell as many times as 223 necessary. 224 225 Arguments: 226 227 start -- int 228 229 end -- int 230 231 Copies are returned, use set_cell() to push them back. 232 """ 233 idx = -1 234 before = -1 235 x = 0 236 cell: Cell 237 if start is None and end is None: 238 for juska in self._rmap: 239 idx += 1 240 if idx in self._indexes["_rmap"]: 241 cell = self._indexes["_rmap"][idx] 242 else: 243 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 244 if not isinstance(cell, Cell): 245 raise TypeError(f"Not a cell: {cell!r}") 246 self._indexes["_rmap"][idx] = cell 247 repeated = juska - before 248 before = juska 249 for _i in range(repeated or 1): 250 # Return a copy without the now obsolete repetition 251 if cell is None: 252 cell = Cell() 253 else: 254 cell = cell.clone 255 if repeated > 1: 256 cell.repeated = None 257 cell.y = self.y 258 cell.x = x 259 x += 1 260 yield cell 261 else: 262 if start is None: 263 start = 0 264 start = max(0, start) 265 if end is None: 266 try: 267 end = self._rmap[-1] 268 except Exception: 269 end = -1 270 start_map = find_odf_idx(self._rmap, start) 271 if start_map is None: 272 return 273 if start_map > 0: 274 before = self._rmap[start_map - 1] 275 idx = start_map - 1 276 before = start - 1 277 x = start 278 for juska in self._rmap[start_map:]: 279 idx += 1 280 if idx in self._indexes["_rmap"]: 281 cell = self._indexes["_rmap"][idx] 282 else: 283 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 284 if not isinstance(cell, Cell): 285 raise TypeError(f"Not a cell: {cell!r}") 286 self._indexes["_rmap"][idx] = cell 287 repeated = juska - before 288 before = juska 289 for _i in range(repeated or 1): 290 if x <= end: 291 if cell is None: 292 cell = Cell() 293 else: 294 cell = cell.clone 295 if repeated > 1 or (x == start and start > 0): 296 cell.repeated = None 297 cell.y = self.y 298 cell.x = x 299 x += 1 300 yield cell 301 302 def get_cells( 303 self, 304 coord: str | tuple | None = None, 305 style: str | None = None, 306 content: str | None = None, 307 cell_type: str | None = None, 308 ) -> list[Cell]: 309 """Get the list of cells matching the criteria. 310 311 Filter by cell_type, with cell_type 'all' will retrieve cells of any 312 type, aka non empty cells. 313 314 Filter by coordinates will retrieve the amount of cells defined by 315 'coord', minus the other filters. 316 317 Arguments: 318 319 coord -- str or tuple of int : coordinates 320 321 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 322 'currency', 'percentage' or 'all' 323 324 content -- str regex 325 326 style -- str 327 328 Return: list of Cell 329 """ 330 # fixme : not clones ? 331 if coord: 332 x, z = self._translate_row_coordinates(coord) 333 else: 334 x = None 335 z = None 336 if cell_type: 337 cell_type = cell_type.lower().strip() 338 cells: list[Cell] = [] 339 for cell in self.traverse(start=x, end=z): 340 # Filter the cells by cell_type 341 if cell_type: 342 ctype = cell.type 343 if not ctype or not (ctype == cell_type or cell_type == "all"): 344 continue 345 # Filter the cells with the regex 346 if content and not cell.match(content): 347 continue 348 # Filter the cells with the style 349 if style and style != cell.style: 350 continue 351 cells.append(cell) 352 return cells 353 354 def _get_cell2(self, x: int, clone: bool = True) -> Cell | None: 355 if x >= self.width: 356 return Cell() 357 if clone: 358 return self._get_cell2_base(x).clone # type: ignore 359 else: 360 return self._get_cell2_base(x) 361 362 def _get_cell2_base(self, x: int) -> Cell | None: 363 idx = find_odf_idx(self._rmap, x) 364 cell: Cell 365 if idx is not None: 366 if idx in self._indexes["_rmap"]: 367 cell = self._indexes["_rmap"][idx] 368 else: 369 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 370 self._indexes["_rmap"][idx] = cell 371 return cell 372 return None 373 374 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 375 """Get the cell at position "x" starting from 0. Alphabetical 376 positions like "D" are accepted. 377 378 A copy is returned, use set_cell() to push it back. 379 380 Arguments: 381 382 x -- int or str 383 384 Return: Cell | None 385 """ 386 x = self._translate_x_from_any(x) 387 cell = self._get_cell2(x, clone=clone) 388 if not cell: 389 return None 390 cell.y = self.y 391 cell.x = x 392 return cell 393 394 def get_value( 395 self, 396 x: int | str, 397 get_type: bool = False, 398 ) -> Any | tuple[Any, str]: 399 """Shortcut to get the value of the cell at position "x". 400 If get_type is True, returns the tuples (value, ODF type). 401 402 If the cell is empty, returns None or (None, None) 403 404 See get_cell() and Cell.get_value(). 405 """ 406 if get_type: 407 x = self._translate_x_from_any(x) 408 cell = self._get_cell2_base(x) 409 if cell is None: 410 return (None, None) 411 return cell.get_value(get_type=get_type) 412 x = self._translate_x_from_any(x) 413 cell = self._get_cell2_base(x) 414 if cell is None: 415 return None 416 return cell.get_value() 417 418 def set_cell( 419 self, 420 x: int | str, 421 cell: Cell | None = None, 422 clone: bool = True, 423 ) -> Cell: 424 """Push the cell back in the row at position "x" starting from 0. 425 Alphabetical positions like "D" are accepted. 426 427 Arguments: 428 429 x -- int or str 430 431 returns the cell with x and y updated 432 """ 433 cell_back: Cell 434 if cell is None: 435 cell = Cell() 436 repeated = 1 437 clone = False 438 else: 439 repeated = cell.repeated or 1 440 x = self._translate_x_from_any(x) 441 # Outside the defined row 442 diff = x - self.width 443 if diff == 0: 444 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 445 elif diff > 0: 446 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 else: 449 # Inside the defined row 450 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 451 cell.x = x 452 cell.y = self.y 453 cell_back = cell 454 return cell_back 455 456 def set_value( 457 self, 458 x: int | str, 459 value: Any, 460 style: str | None = None, 461 cell_type: str | None = None, 462 currency: str | None = None, 463 ) -> None: 464 """Shortcut to set the value of the cell at position "x". 465 466 Arguments: 467 468 x -- int or str 469 470 value -- Python type 471 472 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 473 'string' or 'time' 474 475 currency -- three-letter str 476 477 style -- str 478 479 See get_cell() and Cell.get_value(). 480 """ 481 self.set_cell( 482 x, 483 Cell(value, style=style, cell_type=cell_type, currency=currency), 484 clone=False, 485 ) 486 487 def insert_cell( 488 self, 489 x: int | str, 490 cell: Cell | None = None, 491 clone: bool = True, 492 ) -> Cell: 493 """Insert the given cell at position "x" starting from 0. If no cell 494 is given, an empty one is created. 495 496 Alphabetical positions like "D" are accepted. 497 498 Do not use when working on a table, use Table.insert_cell(). 499 500 Arguments: 501 502 x -- int or str 503 504 cell -- Cell 505 506 returns the cell with x and y updated 507 """ 508 cell_back: Cell 509 if cell is None: 510 cell = Cell() 511 x = self._translate_x_from_any(x) 512 # Outside the defined row 513 diff = x - self.width 514 if diff < 0: 515 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 516 cell.x = x 517 cell.y = self.y 518 cell_back = cell 519 elif diff == 0: 520 cell_back = self.append_cell(cell, clone=clone) 521 else: 522 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 523 cell_back = self.append_cell(cell, clone=clone) 524 return cell_back 525 526 def extend_cells(self, cells: Iterable[Cell] | None = None) -> None: 527 if cells is None: 528 cells = [] 529 self.extend(cells) 530 self._compute_row_cache() 531 532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell 563 564 # fix for unit test and typos 565 append = append_cell # type: ignore 566 567 def delete_cell(self, x: int | str) -> None: 568 """Delete the cell at the given position "x" starting from 0. 569 Alphabetical positions like "D" are accepted. 570 571 Cells on the right will be shifted to the left. In a table, other 572 rows remain unaffected. 573 574 Arguments: 575 576 x -- int or str 577 """ 578 x = self._translate_x_from_any(x) 579 if x >= self.width: 580 return 581 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap") 582 583 def get_values( 584 self, 585 coord: str | tuple | None = None, 586 cell_type: str | None = None, 587 complete: bool = False, 588 get_type: bool = False, 589 ) -> list[Any | tuple[Any, Any]]: 590 """Shortcut to get the cell values in this row. 591 592 Filter by cell_type, with cell_type 'all' will retrieve cells of any 593 type, aka non empty cells. 594 If cell_type is used and complete is True, missing values are 595 replaced by None. 596 If cell_type is None, complete is always True : with no cell type 597 queried, get_values() returns None for each empty cell, the length 598 of the list is equal to the length of the row (depending on 599 coordinates use). 600 601 If get_type is True, returns a tuple (value, ODF type of value), or 602 (None, None) for empty cells if complete is True. 603 604 Filter by coordinates will retrieve the amount of cells defined by 605 coordinates with None for empty cells, except when using cell_type. 606 607 608 Arguments: 609 610 coord -- str or tuple of int : coordinates in row 611 612 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 613 'currency', 'percentage' or 'all' 614 615 complete -- boolean 616 617 get_type -- boolean 618 619 Return: list of Python types, or list of tuples. 620 """ 621 if coord: 622 x, z = self._translate_row_coordinates(coord) 623 else: 624 x = None 625 z = None 626 if cell_type: 627 cell_type = cell_type.lower().strip() 628 values: list[Any | tuple[Any, Any]] = [] 629 for cell in self.traverse(start=x, end=z): 630 # Filter the cells by cell_type 631 ctype = cell.type 632 if not ctype or not (ctype == cell_type or cell_type == "all"): 633 if complete: 634 if get_type: 635 values.append((None, None)) 636 else: 637 values.append(None) 638 continue 639 values.append(cell.get_value(get_type=get_type)) 640 return values 641 else: 642 return [ 643 cell.get_value(get_type=get_type) 644 for cell in self.traverse(start=x, end=z) 645 ] 646 647 def set_cells( 648 self, 649 cells: list[Cell] | tuple[Cell] | None = None, 650 start: int | str = 0, 651 clone: bool = True, 652 ) -> None: 653 """Set the cells in the row, from the 'start' column. 654 This method does not clear the row, use row.clear() before to start 655 with an empty row. 656 657 Arguments: 658 659 cells -- list of cells 660 661 start -- int or str 662 """ 663 if cells is None: 664 cells = [] 665 if start is None: 666 start = 0 667 else: 668 start = self._translate_x_from_any(start) 669 if start == 0 and clone is False and (len(cells) >= self.width): 670 self.clear() 671 self.extend_cells(cells) 672 else: 673 x = start 674 for cell in cells: 675 self.set_cell(x, cell, clone=clone) 676 if cell: 677 x += cell.repeated or 1 678 else: 679 x += 1 680 681 def set_values( 682 self, 683 values: list[Any], 684 start: int | str = 0, 685 style: str | None = None, 686 cell_type: str | None = None, 687 currency: str | None = None, 688 ) -> None: 689 """Shortcut to set the value of cells in the row, from the 'start' 690 column vith values. 691 This method does not clear the row, use row.clear() before to start 692 with an empty row. 693 694 Arguments: 695 696 values -- list of Python types 697 698 start -- int or str 699 700 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 701 'currency' or 'percentage' 702 703 currency -- three-letter str 704 705 style -- cell style 706 """ 707 # fixme : if values n, n+ are same, use repeat 708 if start is None: 709 start = 0 710 else: 711 start = self._translate_x_from_any(start) 712 if start == 0 and (len(values) >= self.width): 713 self.clear() 714 cells = [ 715 Cell(value, style=style, cell_type=cell_type, currency=currency) 716 for value in values 717 ] 718 self.extend_cells(cells) 719 else: 720 x = start 721 for value in values: 722 self.set_cell( 723 x, 724 Cell(value, style=style, cell_type=cell_type, currency=currency), 725 clone=False, 726 ) 727 x += 1 728 729 def rstrip(self, aggressive: bool = False) -> None: 730 """Remove *in-place* empty cells at the right of the row. An empty 731 cell has no value but can have style. If "aggressive" is True, style 732 is ignored. 733 734 Arguments: 735 736 aggressive -- bool 737 """ 738 for cell in reversed(self._get_cells()): 739 if not cell.is_empty(aggressive=aggressive): # type: ignore 740 break 741 self.delete(cell) 742 self._compute_row_cache() 743 self._indexes["_rmap"] = {} 744 745 def is_empty(self, aggressive: bool = False) -> bool: 746 """Return whether every cell in the row has no value or the value 747 evaluates to False (empty string), and no style. 748 749 If aggressive is True, empty cells with style are considered empty. 750 751 Arguments: 752 753 aggressive -- bool 754 755 Return: bool 756 """ 757 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
ODF table row "table:table-row"
59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache()
create a Row, optionally filled with "width" number of cells.
Rows contain cells, their number determine the number of columns.
You don't generally have to create rows by hand, use the Table API.
Arguments:
width -- int
repeated -- int
style -- str
151 @property 152 def repeated(self) -> int | None: 153 """Get / set the number of times the row is repeated. 154 155 Always None when using the table API. 156 157 Return: int or None 158 """ 159 repeated = self.get_attribute("table:number-rows-repeated") 160 if repeated is None: 161 return None 162 return int(repeated)
Get / set the number of times the row is repeated.
Always None when using the table API.
Return: int or None
188 @property 189 def style(self) -> str | None: 190 """Get /set the style of the row itself. 191 192 Return: str 193 """ 194 return self.get_attribute("table:style-name") # type: ignore
Get /set the style of the row itself.
Return: str
200 @property 201 def width(self) -> int: 202 """Get the number of expected cells in the row, i.e. addition 203 repetitions. 204 205 Return: int 206 """ 207 try: 208 value = self._rmap[-1] + 1 209 except Exception: 210 value = 0 211 return value
Get the number of expected cells in the row, i.e. addition repetitions.
Return: int
216 def traverse( # noqa: C901 217 self, 218 start: int | None = None, 219 end: int | None = None, 220 ) -> Iterator[Cell]: 221 """Yield as many cell elements as expected cells in the row, i.e. 222 expand repetitions by returning the same cell as many times as 223 necessary. 224 225 Arguments: 226 227 start -- int 228 229 end -- int 230 231 Copies are returned, use set_cell() to push them back. 232 """ 233 idx = -1 234 before = -1 235 x = 0 236 cell: Cell 237 if start is None and end is None: 238 for juska in self._rmap: 239 idx += 1 240 if idx in self._indexes["_rmap"]: 241 cell = self._indexes["_rmap"][idx] 242 else: 243 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 244 if not isinstance(cell, Cell): 245 raise TypeError(f"Not a cell: {cell!r}") 246 self._indexes["_rmap"][idx] = cell 247 repeated = juska - before 248 before = juska 249 for _i in range(repeated or 1): 250 # Return a copy without the now obsolete repetition 251 if cell is None: 252 cell = Cell() 253 else: 254 cell = cell.clone 255 if repeated > 1: 256 cell.repeated = None 257 cell.y = self.y 258 cell.x = x 259 x += 1 260 yield cell 261 else: 262 if start is None: 263 start = 0 264 start = max(0, start) 265 if end is None: 266 try: 267 end = self._rmap[-1] 268 except Exception: 269 end = -1 270 start_map = find_odf_idx(self._rmap, start) 271 if start_map is None: 272 return 273 if start_map > 0: 274 before = self._rmap[start_map - 1] 275 idx = start_map - 1 276 before = start - 1 277 x = start 278 for juska in self._rmap[start_map:]: 279 idx += 1 280 if idx in self._indexes["_rmap"]: 281 cell = self._indexes["_rmap"][idx] 282 else: 283 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 284 if not isinstance(cell, Cell): 285 raise TypeError(f"Not a cell: {cell!r}") 286 self._indexes["_rmap"][idx] = cell 287 repeated = juska - before 288 before = juska 289 for _i in range(repeated or 1): 290 if x <= end: 291 if cell is None: 292 cell = Cell() 293 else: 294 cell = cell.clone 295 if repeated > 1 or (x == start and start > 0): 296 cell.repeated = None 297 cell.y = self.y 298 cell.x = x 299 x += 1 300 yield cell
Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_cell() to push them back.
302 def get_cells( 303 self, 304 coord: str | tuple | None = None, 305 style: str | None = None, 306 content: str | None = None, 307 cell_type: str | None = None, 308 ) -> list[Cell]: 309 """Get the list of cells matching the criteria. 310 311 Filter by cell_type, with cell_type 'all' will retrieve cells of any 312 type, aka non empty cells. 313 314 Filter by coordinates will retrieve the amount of cells defined by 315 'coord', minus the other filters. 316 317 Arguments: 318 319 coord -- str or tuple of int : coordinates 320 321 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 322 'currency', 'percentage' or 'all' 323 324 content -- str regex 325 326 style -- str 327 328 Return: list of Cell 329 """ 330 # fixme : not clones ? 331 if coord: 332 x, z = self._translate_row_coordinates(coord) 333 else: 334 x = None 335 z = None 336 if cell_type: 337 cell_type = cell_type.lower().strip() 338 cells: list[Cell] = [] 339 for cell in self.traverse(start=x, end=z): 340 # Filter the cells by cell_type 341 if cell_type: 342 ctype = cell.type 343 if not ctype or not (ctype == cell_type or cell_type == "all"): 344 continue 345 # Filter the cells with the regex 346 if content and not cell.match(content): 347 continue 348 # Filter the cells with the style 349 if style and style != cell.style: 350 continue 351 cells.append(cell) 352 return cells
Get the list of cells matching the criteria.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.
Arguments:
coord -- str or tuple of int : coordinates
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
Return: list of Cell
374 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 375 """Get the cell at position "x" starting from 0. Alphabetical 376 positions like "D" are accepted. 377 378 A copy is returned, use set_cell() to push it back. 379 380 Arguments: 381 382 x -- int or str 383 384 Return: Cell | None 385 """ 386 x = self._translate_x_from_any(x) 387 cell = self._get_cell2(x, clone=clone) 388 if not cell: 389 return None 390 cell.y = self.y 391 cell.x = x 392 return cell
Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.
A copy is returned, use set_cell() to push it back.
Arguments:
x -- int or str
Return: Cell | None
394 def get_value( 395 self, 396 x: int | str, 397 get_type: bool = False, 398 ) -> Any | tuple[Any, str]: 399 """Shortcut to get the value of the cell at position "x". 400 If get_type is True, returns the tuples (value, ODF type). 401 402 If the cell is empty, returns None or (None, None) 403 404 See get_cell() and Cell.get_value(). 405 """ 406 if get_type: 407 x = self._translate_x_from_any(x) 408 cell = self._get_cell2_base(x) 409 if cell is None: 410 return (None, None) 411 return cell.get_value(get_type=get_type) 412 x = self._translate_x_from_any(x) 413 cell = self._get_cell2_base(x) 414 if cell is None: 415 return None 416 return cell.get_value()
Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).
If the cell is empty, returns None or (None, None)
See get_cell() and Cell.get_value().
418 def set_cell( 419 self, 420 x: int | str, 421 cell: Cell | None = None, 422 clone: bool = True, 423 ) -> Cell: 424 """Push the cell back in the row at position "x" starting from 0. 425 Alphabetical positions like "D" are accepted. 426 427 Arguments: 428 429 x -- int or str 430 431 returns the cell with x and y updated 432 """ 433 cell_back: Cell 434 if cell is None: 435 cell = Cell() 436 repeated = 1 437 clone = False 438 else: 439 repeated = cell.repeated or 1 440 x = self._translate_x_from_any(x) 441 # Outside the defined row 442 diff = x - self.width 443 if diff == 0: 444 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 445 elif diff > 0: 446 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 else: 449 # Inside the defined row 450 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 451 cell.x = x 452 cell.y = self.y 453 cell_back = cell 454 return cell_back
Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.
Arguments:
x -- int or str
returns the cell with x and y updated
456 def set_value( 457 self, 458 x: int | str, 459 value: Any, 460 style: str | None = None, 461 cell_type: str | None = None, 462 currency: str | None = None, 463 ) -> None: 464 """Shortcut to set the value of the cell at position "x". 465 466 Arguments: 467 468 x -- int or str 469 470 value -- Python type 471 472 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 473 'string' or 'time' 474 475 currency -- three-letter str 476 477 style -- str 478 479 See get_cell() and Cell.get_value(). 480 """ 481 self.set_cell( 482 x, 483 Cell(value, style=style, cell_type=cell_type, currency=currency), 484 clone=False, 485 )
Shortcut to set the value of the cell at position "x".
Arguments:
x -- int or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
See get_cell() and Cell.get_value().
487 def insert_cell( 488 self, 489 x: int | str, 490 cell: Cell | None = None, 491 clone: bool = True, 492 ) -> Cell: 493 """Insert the given cell at position "x" starting from 0. If no cell 494 is given, an empty one is created. 495 496 Alphabetical positions like "D" are accepted. 497 498 Do not use when working on a table, use Table.insert_cell(). 499 500 Arguments: 501 502 x -- int or str 503 504 cell -- Cell 505 506 returns the cell with x and y updated 507 """ 508 cell_back: Cell 509 if cell is None: 510 cell = Cell() 511 x = self._translate_x_from_any(x) 512 # Outside the defined row 513 diff = x - self.width 514 if diff < 0: 515 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 516 cell.x = x 517 cell.y = self.y 518 cell_back = cell 519 elif diff == 0: 520 cell_back = self.append_cell(cell, clone=clone) 521 else: 522 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 523 cell_back = self.append_cell(cell, clone=clone) 524 return cell_back
Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.
Alphabetical positions like "D" are accepted.
Do not use when working on a table, use Table.insert_cell().
Arguments:
x -- int or str
cell -- Cell
returns the cell with x and y updated
532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
567 def delete_cell(self, x: int | str) -> None: 568 """Delete the cell at the given position "x" starting from 0. 569 Alphabetical positions like "D" are accepted. 570 571 Cells on the right will be shifted to the left. In a table, other 572 rows remain unaffected. 573 574 Arguments: 575 576 x -- int or str 577 """ 578 x = self._translate_x_from_any(x) 579 if x >= self.width: 580 return 581 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.
Cells on the right will be shifted to the left. In a table, other rows remain unaffected.
Arguments:
x -- int or str
583 def get_values( 584 self, 585 coord: str | tuple | None = None, 586 cell_type: str | None = None, 587 complete: bool = False, 588 get_type: bool = False, 589 ) -> list[Any | tuple[Any, Any]]: 590 """Shortcut to get the cell values in this row. 591 592 Filter by cell_type, with cell_type 'all' will retrieve cells of any 593 type, aka non empty cells. 594 If cell_type is used and complete is True, missing values are 595 replaced by None. 596 If cell_type is None, complete is always True : with no cell type 597 queried, get_values() returns None for each empty cell, the length 598 of the list is equal to the length of the row (depending on 599 coordinates use). 600 601 If get_type is True, returns a tuple (value, ODF type of value), or 602 (None, None) for empty cells if complete is True. 603 604 Filter by coordinates will retrieve the amount of cells defined by 605 coordinates with None for empty cells, except when using cell_type. 606 607 608 Arguments: 609 610 coord -- str or tuple of int : coordinates in row 611 612 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 613 'currency', 'percentage' or 'all' 614 615 complete -- boolean 616 617 get_type -- boolean 618 619 Return: list of Python types, or list of tuples. 620 """ 621 if coord: 622 x, z = self._translate_row_coordinates(coord) 623 else: 624 x = None 625 z = None 626 if cell_type: 627 cell_type = cell_type.lower().strip() 628 values: list[Any | tuple[Any, Any]] = [] 629 for cell in self.traverse(start=x, end=z): 630 # Filter the cells by cell_type 631 ctype = cell.type 632 if not ctype or not (ctype == cell_type or cell_type == "all"): 633 if complete: 634 if get_type: 635 values.append((None, None)) 636 else: 637 values.append(None) 638 continue 639 values.append(cell.get_value(get_type=get_type)) 640 return values 641 else: 642 return [ 643 cell.get_value(get_type=get_type) 644 for cell in self.traverse(start=x, end=z) 645 ]
Shortcut to get the cell values in this row.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).
If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.
Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.
Arguments:
coord -- str or tuple of int : coordinates in row
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types, or list of tuples.
647 def set_cells( 648 self, 649 cells: list[Cell] | tuple[Cell] | None = None, 650 start: int | str = 0, 651 clone: bool = True, 652 ) -> None: 653 """Set the cells in the row, from the 'start' column. 654 This method does not clear the row, use row.clear() before to start 655 with an empty row. 656 657 Arguments: 658 659 cells -- list of cells 660 661 start -- int or str 662 """ 663 if cells is None: 664 cells = [] 665 if start is None: 666 start = 0 667 else: 668 start = self._translate_x_from_any(start) 669 if start == 0 and clone is False and (len(cells) >= self.width): 670 self.clear() 671 self.extend_cells(cells) 672 else: 673 x = start 674 for cell in cells: 675 self.set_cell(x, cell, clone=clone) 676 if cell: 677 x += cell.repeated or 1 678 else: 679 x += 1
Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
cells -- list of cells
start -- int or str
681 def set_values( 682 self, 683 values: list[Any], 684 start: int | str = 0, 685 style: str | None = None, 686 cell_type: str | None = None, 687 currency: str | None = None, 688 ) -> None: 689 """Shortcut to set the value of cells in the row, from the 'start' 690 column vith values. 691 This method does not clear the row, use row.clear() before to start 692 with an empty row. 693 694 Arguments: 695 696 values -- list of Python types 697 698 start -- int or str 699 700 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 701 'currency' or 'percentage' 702 703 currency -- three-letter str 704 705 style -- cell style 706 """ 707 # fixme : if values n, n+ are same, use repeat 708 if start is None: 709 start = 0 710 else: 711 start = self._translate_x_from_any(start) 712 if start == 0 and (len(values) >= self.width): 713 self.clear() 714 cells = [ 715 Cell(value, style=style, cell_type=cell_type, currency=currency) 716 for value in values 717 ] 718 self.extend_cells(cells) 719 else: 720 x = start 721 for value in values: 722 self.set_cell( 723 x, 724 Cell(value, style=style, cell_type=cell_type, currency=currency), 725 clone=False, 726 ) 727 x += 1
Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
values -- list of Python types
start -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- three-letter str
style -- cell style
729 def rstrip(self, aggressive: bool = False) -> None: 730 """Remove *in-place* empty cells at the right of the row. An empty 731 cell has no value but can have style. If "aggressive" is True, style 732 is ignored. 733 734 Arguments: 735 736 aggressive -- bool 737 """ 738 for cell in reversed(self._get_cells()): 739 if not cell.is_empty(aggressive=aggressive): # type: ignore 740 break 741 self.delete(cell) 742 self._compute_row_cache() 743 self._indexes["_rmap"] = {}
Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.
Arguments:
aggressive -- bool
745 def is_empty(self, aggressive: bool = False) -> bool: 746 """Return whether every cell in the row has no value or the value 747 evaluates to False (empty string), and no style. 748 749 If aggressive is True, empty cells with style are considered empty. 750 751 Arguments: 752 753 aggressive -- bool 754 755 Return: bool 756 """ 757 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
Return: bool
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
133class RowGroup(Element): 134 """ "table:table-row-group" group rows with common properties.""" 135 136 # TODO 137 _tag = "table:table-row-group" 138 _caching = True 139 140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
"table:table-row-group" group rows with common properties.
140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.
Row group bear style information applied to a series of rows.
Arguments:
height -- int
width -- int
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
32class Section(Element): 33 """ODF section "text:section" 34 35 Arguments: 36 37 style -- str 38 39 name -- str 40 """ 41 42 _tag = "text:section" 43 _properties = ( 44 PropDef("style", "text:style-name"), 45 PropDef("name", "text:name"), 46 ) 47 48 def __init__( 49 self, 50 style: str | None = None, 51 name: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 super().__init__(**kwargs) 55 if self._do_init: 56 if style: 57 self.style = style 58 if name: 59 self.name = name 60 61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
ODF section "text:section"
Arguments:
style -- str
name -- str
61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
169class Spacer(Element): 170 """This element shall be used to represent the second and all following “ “ 171 (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. 172 Note: It is not an error if the character preceding the element is not a 173 white space character, but it is good practice to use this element only for 174 the second and all following SPACE characters in a sequence. 175 """ 176 177 _tag = "text:s" 178 _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),) 179 180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.
180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
Arguments:
number -- int
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
799class Span(Paragraph): 800 """Create a span element "text:span" of the given style containing the optional 801 given text. 802 """ 803 804 _tag = "text:span" 805 _properties = ( 806 PropDef("style", "text:style-name"), 807 PropDef("class_names", "text:class-names"), 808 ) 809 810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Create a span element "text:span" of the given style containing the optional given text.
810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Arguments:
text -- str
style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
285class Style(Element): 286 """Style class for all these tags: 287 288 'style:style' 289 'number:date-style', 290 'number:number-style', 291 'number:percentage-style', 292 'number:time-style' 293 'style:font-face', 294 'style:master-page', 295 'style:page-layout', 296 'style:presentation-page-layout', 297 'text:list-style', 298 'text:outline-style', 299 'style:tab-stops', 300 ... 301 """ 302 303 _properties: tuple[PropDef, ...] = ( 304 PropDef("page_layout", "style:page-layout-name", "master-page"), 305 PropDef("next_style", "style:next-style-name", "master-page"), 306 PropDef("name", "style:name"), 307 PropDef("parent_style", "style:parent-style-name"), 308 PropDef("display_name", "style:display-name"), 309 PropDef("svg_font_family", "svg:font-family"), 310 PropDef("font_family_generic", "style:font-family-generic"), 311 PropDef("font_pitch", "style:font-pitch"), 312 PropDef("text_style", "text:style-name"), 313 PropDef("master_page", "style:master-page-name", "paragraph"), 314 PropDef("master_page", "style:master-page-name", "paragraph"), 315 PropDef("master_page", "style:master-page-name", "paragraph"), 316 # style:tab-stop 317 PropDef("style_type", "style:type"), 318 PropDef("leader_style", "style:leader-style"), 319 PropDef("leader_text", "style:leader-text"), 320 PropDef("style_position", "style:position"), 321 PropDef("leader_text", "style:position"), 322 ) 323 324 def __init__( # noqa: C901 325 self, 326 family: str | None = None, 327 name: str | None = None, 328 display_name: str | None = None, 329 parent_style: str | None = None, 330 # Where properties apply 331 area: str | None = None, 332 # For family 'text': 333 color: str | tuple | None = None, 334 background_color: str | tuple | None = None, 335 italic: bool = False, 336 bold: bool = False, 337 # For family 'paragraph' 338 master_page: str | None = None, 339 # For family 'master-page' 340 page_layout: str | None = None, 341 next_style: str | None = None, 342 # For family 'table-cell' 343 data_style: str | None = None, # unused 344 border: str | None = None, 345 border_top: str | None = None, 346 border_right: str | None = None, 347 border_bottom: str | None = None, 348 border_left: str | None = None, 349 padding: str | None = None, 350 padding_top: str | None = None, 351 padding_bottom: str | None = None, 352 padding_left: str | None = None, 353 padding_right: str | None = None, 354 shadow: str | None = None, 355 # For family 'table-row' 356 height: str | None = None, 357 use_optimal_height: bool = False, 358 # For family 'table-column' 359 width: str | None = None, 360 break_before: str | None = None, 361 break_after: str | None = None, 362 # For family 'graphic' 363 min_height: str | None = None, 364 # For family 'font-face' 365 font_name: str | None = None, 366 font_family: str | None = None, 367 font_family_generic: str | None = None, 368 font_pitch: str = "variable", 369 # Every other property 370 **kwargs: Any, 371 ) -> None: 372 """Create a style of the given family. The name is not mandatory at this 373 point but will become required when inserting in a document as a common 374 style. 375 376 The display name is the name the user sees in an office application. 377 378 The parent_style is the name of the style this style will inherit from. 379 380 To set properties, pass them as keyword arguments. The area properties 381 apply to is optional and defaults to the family. 382 383 Arguments: 384 385 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 386 'table-row', 'table-cell', 'table-page', 'chart', 387 'drawing-page', 'graphic', 'presentation', 388 'control', 'ruby', 'list', 'number', 'page-layout' 389 'font-face', or 'master-page' 390 391 name -- str 392 393 display_name -- str 394 395 parent_style -- str 396 397 area -- str 398 399 'text' Properties: 400 401 italic -- bool 402 403 bold -- bool 404 405 'paragraph' Properties: 406 407 master_page -- str 408 409 'master-page' Properties: 410 411 page_layout -- str 412 413 next_style -- str 414 415 'table-cell' Properties: 416 417 border, border_top, border_right, border_bottom, border_left -- str, 418 e.g. "0.002cm solid #000000" or 'none' 419 420 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 421 e.g. "0.002cm" or 'none' 422 423 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 424 425 'table-row' Properties: 426 427 height -- str, e.g. '5cm' 428 429 use_optimal_height -- bool 430 431 'table-column' Properties: 432 433 width -- str, e.g. '5cm' 434 435 break_before -- 'page', 'column' or 'auto' 436 437 break_after -- 'page', 'column' or 'auto' 438 """ 439 self._family: str | None = None 440 tag_or_elem = kwargs.get("tag_or_elem", None) 441 if tag_or_elem is None: 442 family = to_str(family) 443 if family not in FAMILY_MAPPING: 444 raise ValueError("Unknown family value: %s" % family) 445 kwargs["tag"] = FAMILY_MAPPING[family] 446 super().__init__(**kwargs) 447 if self._do_init and family not in SUBCLASSED_STYLES: 448 kwargs.pop("tag", None) 449 kwargs.pop("tag_or_elem", None) 450 self.family = family # relevant test made by property 451 # Common attributes 452 if name: 453 self.name = name 454 if display_name: 455 self.display_name = display_name 456 if parent_style: 457 self.parent_style = parent_style 458 # Paragraph 459 if family == "paragraph": 460 if master_page: 461 self.master_page = master_page 462 # Master Page 463 elif family == "master-page": 464 if page_layout: 465 self.page_layout = page_layout 466 if next_style: 467 self.next_style = next_style 468 # Font face 469 elif family == "font-face": 470 if not font_name: 471 raise ValueError("A font_name is required for 'font-face' style") 472 self.set_font( 473 font_name, 474 family=font_family, 475 family_generic=font_family_generic, 476 pitch=font_pitch, 477 ) 478 # Properties 479 if area is None: 480 area = family 481 area = to_str(area) 482 # Text 483 if area == "text": 484 if color: 485 kwargs["fo:color"] = color 486 if background_color: 487 kwargs["fo:background-color"] = background_color 488 if italic: 489 kwargs["fo:font-style"] = "italic" 490 kwargs["style:font-style-asian"] = "italic" 491 kwargs["style:font-style-complex"] = "italic" 492 if bold: 493 kwargs["fo:font-weight"] = "bold" 494 kwargs["style:font-weight-asian"] = "bold" 495 kwargs["style:font-weight-complex"] = "bold" 496 # Table cell 497 elif area == "table-cell": 498 if border: 499 kwargs["fo:border"] = border 500 elif border_top or border_right or border_bottom or border_left: 501 kwargs["fo:border-top"] = border_top or "none" 502 kwargs["fo:border-right"] = border_right or "none" 503 kwargs["fo:border-bottom"] = border_bottom or "none" 504 kwargs["fo:border-left"] = border_left or "none" 505 else: # no border_top, ... neither border are defined 506 pass # left untouched 507 if padding: 508 kwargs["fo:padding"] = padding 509 elif padding_top or padding_right or padding_bottom or padding_left: 510 kwargs["fo:padding-top"] = padding_top or "none" 511 kwargs["fo:padding-right"] = padding_right or "none" 512 kwargs["fo:padding-bottom"] = padding_bottom or "none" 513 kwargs["fo:padding-left"] = padding_left or "none" 514 else: # no border_top, ... neither border are defined 515 pass # left untouched 516 if shadow: 517 kwargs["style:shadow"] = shadow 518 if background_color: 519 kwargs["fo:background-color"] = background_color 520 # Table row 521 elif area == "table-row": 522 if height: 523 kwargs["style:row-height"] = height 524 if use_optimal_height: 525 kwargs["style:use-optimal-row-height"] = Boolean.encode( 526 use_optimal_height 527 ) 528 if background_color: 529 kwargs["fo:background-color"] = background_color 530 # Table column 531 elif area == "table-column": 532 if width: 533 kwargs["style:column-width"] = width 534 if break_before: 535 kwargs["fo:break-before"] = break_before 536 if break_after: 537 kwargs["fo:break-after"] = break_after 538 # Graphic 539 elif area == "graphic": 540 if min_height: 541 kwargs["fo:min-height"] = min_height 542 # Every other properties 543 if kwargs: 544 self.set_properties(kwargs, area=area) 545 546 @property 547 def family(self) -> str | None: 548 if self._family is None: 549 self._family = FALSE_FAMILY_MAP_REVERSE.get( 550 self.tag, self.get_attribute_string("style:family") 551 ) 552 return self._family 553 554 @family.setter 555 def family(self, family: str | None) -> None: 556 self._family = family 557 if family in FAMILY_ODF_STD and self.tag == "style:style": 558 self.set_attribute("style:family", family) 559 560 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 561 """Get the mapping of all properties of this style. By default the 562 properties of the same family, e.g. a paragraph style and its 563 paragraph properties. Specify the area to get the text properties of 564 a paragraph style for example. 565 566 Arguments: 567 568 area -- str 569 570 Return: dict 571 """ 572 if area is None: 573 area = self.family 574 element = self.get_element(f"style:{area}-properties") 575 if element is None: 576 return None 577 properties: dict[str, str | dict] = element.attributes # type: ignore 578 # Nested properties are nested dictionaries 579 for child in element.children: 580 properties[child.tag] = child.attributes 581 return properties 582 583 def set_properties( # noqa: C901 584 self, 585 properties: dict[str, str | dict] | None = None, 586 style: Style | None = None, 587 area: str | None = None, 588 **kwargs: Any, 589 ) -> None: 590 """Set the properties of the "area" type of this style. Properties 591 are given either as a dict or as named arguments (or both). The area 592 is identical to the style family by default. If the properties 593 element is missing, it is created. 594 595 Instead of properties, you can pass a style with properties of the 596 same area. These will be copied. 597 598 Arguments: 599 600 properties -- dict 601 602 style -- Style 603 604 area -- 'paragraph', 'text'... 605 """ 606 if properties is None: 607 properties = {} 608 if area is None: 609 if isinstance(self.family, bool): 610 area = None 611 else: 612 area = self.family 613 element = self.get_element(f"style:{area}-properties") 614 if element is None: 615 element = Element.from_tag(f"style:{area}-properties") 616 self.append(element) 617 if properties or kwargs: 618 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 619 elif style is not None: 620 properties = style.get_properties(area=area) 621 if properties is None: 622 return 623 if properties is None: 624 return 625 for key, value in properties.items(): 626 if value is None: 627 element.del_attribute(key) 628 elif isinstance(value, (str, bool, tuple)): 629 element.set_attribute(key, value) 630 else: 631 pass 632 633 def del_properties( 634 self, 635 properties: list[str] | None = None, 636 area: str | None = None, 637 ) -> None: 638 """Delete the given properties, either by list argument or 639 positional argument (or both). Remove only from the given area, 640 identical to the style family by default. 641 642 Arguments: 643 644 properties -- list 645 646 area -- str 647 """ 648 if properties is None: 649 properties = [] 650 if area is None: 651 area = self.family 652 element = self.get_element(f"style:{area}-properties") 653 if element is None: 654 raise ValueError( 655 f"properties element is inexistent for: style:{area}-properties" 656 ) 657 for key in _expand_properties_list(properties): 658 element.del_attribute(key) 659 660 def set_background( # noqa: C901 661 self, 662 color: str | None = None, 663 url: str | None = None, 664 position: str | None = "center", 665 repeat: str | None = None, 666 opacity: str | None = None, 667 filter: str | None = None, # noqa: A002 668 ) -> None: 669 """Set the background color of a text style, or the background color 670 or image of a paragraph style or page layout. 671 672 With no argument, remove any existing background. 673 674 The position is one or two of 'center', 'left', 'right', 'top' or 675 'bottom'. 676 677 The repeat is 'no-repeat', 'repeat' or 'stretch'. 678 679 The opacity is a percentage integer (not a string with the '%s' sign) 680 681 The filter is an application-specific filter name defined elsewhere. 682 683 Though this method is defined on the base style class, it will raise 684 an error if the style type is not compatible. 685 686 Arguments: 687 688 color -- '#rrggbb' 689 690 url -- str 691 692 position -- str 693 694 repeat -- str 695 696 opacity -- int 697 698 filter -- str 699 """ 700 family = self.family 701 if family not in { 702 "text", 703 "paragraph", 704 "page-layout", 705 "section", 706 "table", 707 "table-row", 708 "table-cell", 709 "graphic", 710 }: 711 raise TypeError("No background support for this family") 712 if url is not None and family == "text": 713 raise TypeError("No background image for text styles") 714 properties = self.get_element(f"style:{family}-properties") 715 bg_image: BackgroundImage | None = None 716 if properties is not None: 717 bg_image = properties.get_element("style:background-image") # type:ignore 718 # Erasing 719 if color is None and url is None: 720 if properties is None: 721 return 722 properties.del_attribute("fo:background-color") 723 if bg_image is not None: 724 properties.delete(bg_image) 725 return 726 # Add the properties if necessary 727 if properties is None: 728 properties = Element.from_tag(f"style:{family}-properties") 729 self.append(properties) 730 # Add the color... 731 if color: 732 properties.set_attribute("fo:background-color", color) 733 if bg_image is not None: 734 properties.delete(bg_image) 735 # ... or the background 736 elif url: 737 properties.set_attribute("fo:background-color", "transparent") 738 if bg_image is None: 739 bg_image = Element.from_tag("style:background-image") # type:ignore 740 properties.append(bg_image) # type:ignore 741 bg_image.url = url # type:ignore 742 if position: 743 bg_image.position = position # type:ignore 744 if repeat: 745 bg_image.repeat = repeat # type:ignore 746 if opacity: 747 bg_image.opacity = opacity # type:ignore 748 if filter: 749 bg_image.filter = filter # type:ignore 750 751 # list-style only: 752 753 def get_level_style(self, level: int) -> Style | None: 754 if self.family != "list": 755 return None 756 level_styles = ( 757 "(text:list-level-style-number" 758 "|text:list-level-style-bullet" 759 "|text:list-level-style-image)" 760 ) 761 return self._filtered_element(level_styles, 0, level=level) # type: ignore 762 763 def set_level_style( # noqa: C901 764 self, 765 level: int, 766 num_format: str | None = None, 767 bullet_char: str | None = None, 768 url: str | None = None, 769 display_levels: int | None = None, 770 prefix: str | None = None, 771 suffix: str | None = None, 772 start_value: int | None = None, 773 style: str | None = None, 774 clone: Style | None = None, 775 ) -> Style | None: 776 """ 777 Arguments: 778 779 level -- int 780 781 num_format (for number) -- int 782 783 bullet_char (for bullet) -- str 784 785 url (for image) -- str 786 787 display_levels -- int 788 789 prefix -- str 790 791 suffix -- str 792 793 start_value -- int 794 795 style -- str 796 797 clone -- List Style 798 799 Return: 800 level_style created 801 """ 802 if self.family != "list": 803 return None 804 # Expected name 805 if num_format is not None: 806 level_style_name = "text:list-level-style-number" 807 elif bullet_char is not None: 808 level_style_name = "text:list-level-style-bullet" 809 elif url is not None: 810 level_style_name = "text:list-level-style-image" 811 elif clone is not None: 812 level_style_name = clone.tag 813 else: 814 raise ValueError("unknown level style type") 815 was_created = False 816 # Cloning or reusing an existing element 817 level_style: Style | None = None 818 if clone is not None: 819 level_style = clone.clone # type: ignore 820 was_created = True 821 else: 822 level_style = self.get_level_style(level) 823 if level_style is None: 824 level_style = Element.from_tag(level_style_name) # type: ignore 825 was_created = True 826 if level_style is None: 827 return None 828 # Transmute if the type changed 829 if level_style.tag != level_style_name: 830 print("Warn: different style", level_style_name, level_style.tag) 831 level_style.tag = level_style_name 832 # Set the level 833 level_style.set_attribute("text:level", str(level)) 834 # Set the main attribute 835 if num_format is not None: 836 level_style.set_attribute("fo:num-format", num_format) 837 elif bullet_char is not None: 838 level_style.set_attribute("text:bullet-char", bullet_char) 839 elif url is not None: 840 level_style.set_attribute("xlink:href", url) 841 # Set attributes 842 if prefix: 843 level_style.set_attribute("style:num-prefix", prefix) 844 if suffix: 845 level_style.set_attribute("style:num-suffix", suffix) 846 if display_levels: 847 level_style.set_attribute("text:display-levels", str(display_levels)) 848 if start_value: 849 level_style.set_attribute("text:start-value", str(start_value)) 850 if style: 851 level_style.text_style = style # type: ignore 852 # Commit the creation 853 if was_created: 854 self.append(level_style) 855 return level_style 856 857 # page-layout only: 858 859 def get_header_style(self) -> Element | None: 860 if self.family != "page-layout": 861 return None 862 return self.get_element("style:header-style") 863 864 def set_header_style(self, new_style: Style) -> None: 865 if self.family != "page-layout": 866 return 867 header_style = self.get_header_style() 868 if header_style is not None: 869 self.delete(header_style) 870 self.append(new_style) 871 872 def get_footer_style(self) -> Style | None: 873 if self.family != "page-layout": 874 return None 875 return self.get_element("style:footer-style") # type: ignore 876 877 def set_footer_style(self, new_style: Style) -> None: 878 if self.family != "page-layout": 879 return 880 footer_style = self.get_footer_style() 881 if footer_style is not None: 882 self.delete(footer_style) 883 self.append(new_style) 884 885 # master-page only: 886 887 def _set_header_or_footer( 888 self, 889 text_or_element: str | Element | list[Element | str], 890 name: str = "header", 891 style: str = "Header", 892 ) -> None: 893 if name == "header": 894 header_or_footer = self.get_page_header() 895 else: 896 header_or_footer = self.get_page_footer() 897 if header_or_footer is None: 898 header_or_footer = Element.from_tag("style:" + name) 899 self.append(header_or_footer) 900 else: 901 header_or_footer.clear() 902 if ( 903 isinstance(text_or_element, Element) 904 and text_or_element.tag == f"style:{name}" 905 ): 906 # Already a header or footer? 907 self.delete(header_or_footer) 908 self.append(text_or_element) 909 return 910 if isinstance(text_or_element, (Element, str)): 911 elem_list: list[Element | str] = [text_or_element] 912 else: 913 elem_list = text_or_element 914 for item in elem_list: 915 if isinstance(item, str): 916 paragraph = Element.from_tag("text:p") 917 paragraph.style = style # type: ignore 918 header_or_footer.append(paragraph) 919 elif isinstance(item, Element): 920 header_or_footer.append(item) 921 922 def get_page_header(self) -> Element | None: 923 """Get the element that contains the header contents. 924 925 If None, no header was set. 926 """ 927 if self.family != "master-page": 928 return None 929 return self.get_element("style:header") 930 931 def set_page_header( 932 self, 933 text_or_element: str | Element | list[Element | str], 934 ) -> None: 935 """Create or replace the header by the given content. It can already 936 be a complete header. 937 938 If you only want to update the existing header, get it and use the 939 API. 940 941 Arguments: 942 943 text_or_element -- str or Element or a list of them 944 """ 945 if self.family != "master-page": 946 return None 947 self._set_header_or_footer(text_or_element) 948 949 def get_page_footer(self) -> Element | None: 950 """Get the element that contains the footer contents. 951 952 If None, no footer was set. 953 """ 954 if self.family != "master-page": 955 return None 956 return self.get_element("style:footer") 957 958 def set_page_footer( 959 self, 960 text_or_element: str | Element | list[Element | str], 961 ) -> None: 962 """Create or replace the footer by the given content. It can already 963 be a complete footer. 964 965 If you only want to update the existing footer, get it and use the 966 API. 967 968 Arguments: 969 970 text_or_element -- str or Element or a list of them 971 """ 972 if self.family != "master-page": 973 return None 974 self._set_header_or_footer(text_or_element, name="footer", style="Footer") 975 976 # font-face only: 977 978 def set_font( 979 self, 980 name: str, 981 family: str | None = None, 982 family_generic: str | None = None, 983 pitch: str = "variable", 984 ) -> None: 985 if self.family != "font-face": 986 return 987 self.name = name 988 if family is None: 989 family = name 990 self.svg_font_family = f'"{family}"' 991 if family_generic is not None: 992 self.font_family_generic = family_generic 993 self.font_pitch = pitch
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
324 def __init__( # noqa: C901 325 self, 326 family: str | None = None, 327 name: str | None = None, 328 display_name: str | None = None, 329 parent_style: str | None = None, 330 # Where properties apply 331 area: str | None = None, 332 # For family 'text': 333 color: str | tuple | None = None, 334 background_color: str | tuple | None = None, 335 italic: bool = False, 336 bold: bool = False, 337 # For family 'paragraph' 338 master_page: str | None = None, 339 # For family 'master-page' 340 page_layout: str | None = None, 341 next_style: str | None = None, 342 # For family 'table-cell' 343 data_style: str | None = None, # unused 344 border: str | None = None, 345 border_top: str | None = None, 346 border_right: str | None = None, 347 border_bottom: str | None = None, 348 border_left: str | None = None, 349 padding: str | None = None, 350 padding_top: str | None = None, 351 padding_bottom: str | None = None, 352 padding_left: str | None = None, 353 padding_right: str | None = None, 354 shadow: str | None = None, 355 # For family 'table-row' 356 height: str | None = None, 357 use_optimal_height: bool = False, 358 # For family 'table-column' 359 width: str | None = None, 360 break_before: str | None = None, 361 break_after: str | None = None, 362 # For family 'graphic' 363 min_height: str | None = None, 364 # For family 'font-face' 365 font_name: str | None = None, 366 font_family: str | None = None, 367 font_family_generic: str | None = None, 368 font_pitch: str = "variable", 369 # Every other property 370 **kwargs: Any, 371 ) -> None: 372 """Create a style of the given family. The name is not mandatory at this 373 point but will become required when inserting in a document as a common 374 style. 375 376 The display name is the name the user sees in an office application. 377 378 The parent_style is the name of the style this style will inherit from. 379 380 To set properties, pass them as keyword arguments. The area properties 381 apply to is optional and defaults to the family. 382 383 Arguments: 384 385 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 386 'table-row', 'table-cell', 'table-page', 'chart', 387 'drawing-page', 'graphic', 'presentation', 388 'control', 'ruby', 'list', 'number', 'page-layout' 389 'font-face', or 'master-page' 390 391 name -- str 392 393 display_name -- str 394 395 parent_style -- str 396 397 area -- str 398 399 'text' Properties: 400 401 italic -- bool 402 403 bold -- bool 404 405 'paragraph' Properties: 406 407 master_page -- str 408 409 'master-page' Properties: 410 411 page_layout -- str 412 413 next_style -- str 414 415 'table-cell' Properties: 416 417 border, border_top, border_right, border_bottom, border_left -- str, 418 e.g. "0.002cm solid #000000" or 'none' 419 420 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 421 e.g. "0.002cm" or 'none' 422 423 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 424 425 'table-row' Properties: 426 427 height -- str, e.g. '5cm' 428 429 use_optimal_height -- bool 430 431 'table-column' Properties: 432 433 width -- str, e.g. '5cm' 434 435 break_before -- 'page', 'column' or 'auto' 436 437 break_after -- 'page', 'column' or 'auto' 438 """ 439 self._family: str | None = None 440 tag_or_elem = kwargs.get("tag_or_elem", None) 441 if tag_or_elem is None: 442 family = to_str(family) 443 if family not in FAMILY_MAPPING: 444 raise ValueError("Unknown family value: %s" % family) 445 kwargs["tag"] = FAMILY_MAPPING[family] 446 super().__init__(**kwargs) 447 if self._do_init and family not in SUBCLASSED_STYLES: 448 kwargs.pop("tag", None) 449 kwargs.pop("tag_or_elem", None) 450 self.family = family # relevant test made by property 451 # Common attributes 452 if name: 453 self.name = name 454 if display_name: 455 self.display_name = display_name 456 if parent_style: 457 self.parent_style = parent_style 458 # Paragraph 459 if family == "paragraph": 460 if master_page: 461 self.master_page = master_page 462 # Master Page 463 elif family == "master-page": 464 if page_layout: 465 self.page_layout = page_layout 466 if next_style: 467 self.next_style = next_style 468 # Font face 469 elif family == "font-face": 470 if not font_name: 471 raise ValueError("A font_name is required for 'font-face' style") 472 self.set_font( 473 font_name, 474 family=font_family, 475 family_generic=font_family_generic, 476 pitch=font_pitch, 477 ) 478 # Properties 479 if area is None: 480 area = family 481 area = to_str(area) 482 # Text 483 if area == "text": 484 if color: 485 kwargs["fo:color"] = color 486 if background_color: 487 kwargs["fo:background-color"] = background_color 488 if italic: 489 kwargs["fo:font-style"] = "italic" 490 kwargs["style:font-style-asian"] = "italic" 491 kwargs["style:font-style-complex"] = "italic" 492 if bold: 493 kwargs["fo:font-weight"] = "bold" 494 kwargs["style:font-weight-asian"] = "bold" 495 kwargs["style:font-weight-complex"] = "bold" 496 # Table cell 497 elif area == "table-cell": 498 if border: 499 kwargs["fo:border"] = border 500 elif border_top or border_right or border_bottom or border_left: 501 kwargs["fo:border-top"] = border_top or "none" 502 kwargs["fo:border-right"] = border_right or "none" 503 kwargs["fo:border-bottom"] = border_bottom or "none" 504 kwargs["fo:border-left"] = border_left or "none" 505 else: # no border_top, ... neither border are defined 506 pass # left untouched 507 if padding: 508 kwargs["fo:padding"] = padding 509 elif padding_top or padding_right or padding_bottom or padding_left: 510 kwargs["fo:padding-top"] = padding_top or "none" 511 kwargs["fo:padding-right"] = padding_right or "none" 512 kwargs["fo:padding-bottom"] = padding_bottom or "none" 513 kwargs["fo:padding-left"] = padding_left or "none" 514 else: # no border_top, ... neither border are defined 515 pass # left untouched 516 if shadow: 517 kwargs["style:shadow"] = shadow 518 if background_color: 519 kwargs["fo:background-color"] = background_color 520 # Table row 521 elif area == "table-row": 522 if height: 523 kwargs["style:row-height"] = height 524 if use_optimal_height: 525 kwargs["style:use-optimal-row-height"] = Boolean.encode( 526 use_optimal_height 527 ) 528 if background_color: 529 kwargs["fo:background-color"] = background_color 530 # Table column 531 elif area == "table-column": 532 if width: 533 kwargs["style:column-width"] = width 534 if break_before: 535 kwargs["fo:break-before"] = break_before 536 if break_after: 537 kwargs["fo:break-after"] = break_after 538 # Graphic 539 elif area == "graphic": 540 if min_height: 541 kwargs["fo:min-height"] = min_height 542 # Every other properties 543 if kwargs: 544 self.set_properties(kwargs, area=area)
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
560 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 561 """Get the mapping of all properties of this style. By default the 562 properties of the same family, e.g. a paragraph style and its 563 paragraph properties. Specify the area to get the text properties of 564 a paragraph style for example. 565 566 Arguments: 567 568 area -- str 569 570 Return: dict 571 """ 572 if area is None: 573 area = self.family 574 element = self.get_element(f"style:{area}-properties") 575 if element is None: 576 return None 577 properties: dict[str, str | dict] = element.attributes # type: ignore 578 # Nested properties are nested dictionaries 579 for child in element.children: 580 properties[child.tag] = child.attributes 581 return properties
Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.
Arguments:
area -- str
Return: dict
583 def set_properties( # noqa: C901 584 self, 585 properties: dict[str, str | dict] | None = None, 586 style: Style | None = None, 587 area: str | None = None, 588 **kwargs: Any, 589 ) -> None: 590 """Set the properties of the "area" type of this style. Properties 591 are given either as a dict or as named arguments (or both). The area 592 is identical to the style family by default. If the properties 593 element is missing, it is created. 594 595 Instead of properties, you can pass a style with properties of the 596 same area. These will be copied. 597 598 Arguments: 599 600 properties -- dict 601 602 style -- Style 603 604 area -- 'paragraph', 'text'... 605 """ 606 if properties is None: 607 properties = {} 608 if area is None: 609 if isinstance(self.family, bool): 610 area = None 611 else: 612 area = self.family 613 element = self.get_element(f"style:{area}-properties") 614 if element is None: 615 element = Element.from_tag(f"style:{area}-properties") 616 self.append(element) 617 if properties or kwargs: 618 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 619 elif style is not None: 620 properties = style.get_properties(area=area) 621 if properties is None: 622 return 623 if properties is None: 624 return 625 for key, value in properties.items(): 626 if value is None: 627 element.del_attribute(key) 628 elif isinstance(value, (str, bool, tuple)): 629 element.set_attribute(key, value) 630 else: 631 pass
Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.
Instead of properties, you can pass a style with properties of the same area. These will be copied.
Arguments:
properties -- dict
style -- Style
area -- 'paragraph', 'text'...
633 def del_properties( 634 self, 635 properties: list[str] | None = None, 636 area: str | None = None, 637 ) -> None: 638 """Delete the given properties, either by list argument or 639 positional argument (or both). Remove only from the given area, 640 identical to the style family by default. 641 642 Arguments: 643 644 properties -- list 645 646 area -- str 647 """ 648 if properties is None: 649 properties = [] 650 if area is None: 651 area = self.family 652 element = self.get_element(f"style:{area}-properties") 653 if element is None: 654 raise ValueError( 655 f"properties element is inexistent for: style:{area}-properties" 656 ) 657 for key in _expand_properties_list(properties): 658 element.del_attribute(key)
Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.
Arguments:
properties -- list
area -- str
660 def set_background( # noqa: C901 661 self, 662 color: str | None = None, 663 url: str | None = None, 664 position: str | None = "center", 665 repeat: str | None = None, 666 opacity: str | None = None, 667 filter: str | None = None, # noqa: A002 668 ) -> None: 669 """Set the background color of a text style, or the background color 670 or image of a paragraph style or page layout. 671 672 With no argument, remove any existing background. 673 674 The position is one or two of 'center', 'left', 'right', 'top' or 675 'bottom'. 676 677 The repeat is 'no-repeat', 'repeat' or 'stretch'. 678 679 The opacity is a percentage integer (not a string with the '%s' sign) 680 681 The filter is an application-specific filter name defined elsewhere. 682 683 Though this method is defined on the base style class, it will raise 684 an error if the style type is not compatible. 685 686 Arguments: 687 688 color -- '#rrggbb' 689 690 url -- str 691 692 position -- str 693 694 repeat -- str 695 696 opacity -- int 697 698 filter -- str 699 """ 700 family = self.family 701 if family not in { 702 "text", 703 "paragraph", 704 "page-layout", 705 "section", 706 "table", 707 "table-row", 708 "table-cell", 709 "graphic", 710 }: 711 raise TypeError("No background support for this family") 712 if url is not None and family == "text": 713 raise TypeError("No background image for text styles") 714 properties = self.get_element(f"style:{family}-properties") 715 bg_image: BackgroundImage | None = None 716 if properties is not None: 717 bg_image = properties.get_element("style:background-image") # type:ignore 718 # Erasing 719 if color is None and url is None: 720 if properties is None: 721 return 722 properties.del_attribute("fo:background-color") 723 if bg_image is not None: 724 properties.delete(bg_image) 725 return 726 # Add the properties if necessary 727 if properties is None: 728 properties = Element.from_tag(f"style:{family}-properties") 729 self.append(properties) 730 # Add the color... 731 if color: 732 properties.set_attribute("fo:background-color", color) 733 if bg_image is not None: 734 properties.delete(bg_image) 735 # ... or the background 736 elif url: 737 properties.set_attribute("fo:background-color", "transparent") 738 if bg_image is None: 739 bg_image = Element.from_tag("style:background-image") # type:ignore 740 properties.append(bg_image) # type:ignore 741 bg_image.url = url # type:ignore 742 if position: 743 bg_image.position = position # type:ignore 744 if repeat: 745 bg_image.repeat = repeat # type:ignore 746 if opacity: 747 bg_image.opacity = opacity # type:ignore 748 if filter: 749 bg_image.filter = filter # type:ignore
Set the background color of a text style, or the background color or image of a paragraph style or page layout.
With no argument, remove any existing background.
The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.
The repeat is 'no-repeat', 'repeat' or 'stretch'.
The opacity is a percentage integer (not a string with the '%s' sign)
The filter is an application-specific filter name defined elsewhere.
Though this method is defined on the base style class, it will raise an error if the style type is not compatible.
Arguments:
color -- '#rrggbb'
url -- str
position -- str
repeat -- str
opacity -- int
filter -- str
753 def get_level_style(self, level: int) -> Style | None: 754 if self.family != "list": 755 return None 756 level_styles = ( 757 "(text:list-level-style-number" 758 "|text:list-level-style-bullet" 759 "|text:list-level-style-image)" 760 ) 761 return self._filtered_element(level_styles, 0, level=level) # type: ignore
763 def set_level_style( # noqa: C901 764 self, 765 level: int, 766 num_format: str | None = None, 767 bullet_char: str | None = None, 768 url: str | None = None, 769 display_levels: int | None = None, 770 prefix: str | None = None, 771 suffix: str | None = None, 772 start_value: int | None = None, 773 style: str | None = None, 774 clone: Style | None = None, 775 ) -> Style | None: 776 """ 777 Arguments: 778 779 level -- int 780 781 num_format (for number) -- int 782 783 bullet_char (for bullet) -- str 784 785 url (for image) -- str 786 787 display_levels -- int 788 789 prefix -- str 790 791 suffix -- str 792 793 start_value -- int 794 795 style -- str 796 797 clone -- List Style 798 799 Return: 800 level_style created 801 """ 802 if self.family != "list": 803 return None 804 # Expected name 805 if num_format is not None: 806 level_style_name = "text:list-level-style-number" 807 elif bullet_char is not None: 808 level_style_name = "text:list-level-style-bullet" 809 elif url is not None: 810 level_style_name = "text:list-level-style-image" 811 elif clone is not None: 812 level_style_name = clone.tag 813 else: 814 raise ValueError("unknown level style type") 815 was_created = False 816 # Cloning or reusing an existing element 817 level_style: Style | None = None 818 if clone is not None: 819 level_style = clone.clone # type: ignore 820 was_created = True 821 else: 822 level_style = self.get_level_style(level) 823 if level_style is None: 824 level_style = Element.from_tag(level_style_name) # type: ignore 825 was_created = True 826 if level_style is None: 827 return None 828 # Transmute if the type changed 829 if level_style.tag != level_style_name: 830 print("Warn: different style", level_style_name, level_style.tag) 831 level_style.tag = level_style_name 832 # Set the level 833 level_style.set_attribute("text:level", str(level)) 834 # Set the main attribute 835 if num_format is not None: 836 level_style.set_attribute("fo:num-format", num_format) 837 elif bullet_char is not None: 838 level_style.set_attribute("text:bullet-char", bullet_char) 839 elif url is not None: 840 level_style.set_attribute("xlink:href", url) 841 # Set attributes 842 if prefix: 843 level_style.set_attribute("style:num-prefix", prefix) 844 if suffix: 845 level_style.set_attribute("style:num-suffix", suffix) 846 if display_levels: 847 level_style.set_attribute("text:display-levels", str(display_levels)) 848 if start_value: 849 level_style.set_attribute("text:start-value", str(start_value)) 850 if style: 851 level_style.text_style = style # type: ignore 852 # Commit the creation 853 if was_created: 854 self.append(level_style) 855 return level_style
Arguments:
level -- int
num_format (for number) -- int
bullet_char (for bullet) -- str
url (for image) -- str
display_levels -- int
prefix -- str
suffix -- str
start_value -- int
style -- str
clone -- List Style
Return: level_style created
922 def get_page_header(self) -> Element | None: 923 """Get the element that contains the header contents. 924 925 If None, no header was set. 926 """ 927 if self.family != "master-page": 928 return None 929 return self.get_element("style:header")
Get the element that contains the header contents.
If None, no header was set.
931 def set_page_header( 932 self, 933 text_or_element: str | Element | list[Element | str], 934 ) -> None: 935 """Create or replace the header by the given content. It can already 936 be a complete header. 937 938 If you only want to update the existing header, get it and use the 939 API. 940 941 Arguments: 942 943 text_or_element -- str or Element or a list of them 944 """ 945 if self.family != "master-page": 946 return None 947 self._set_header_or_footer(text_or_element)
Create or replace the header by the given content. It can already be a complete header.
If you only want to update the existing header, get it and use the API.
Arguments:
text_or_element -- str or Element or a list of them
978 def set_font( 979 self, 980 name: str, 981 family: str | None = None, 982 family_generic: str | None = None, 983 pitch: str = "variable", 984 ) -> None: 985 if self.family != "font-face": 986 return 987 self.name = name 988 if family is None: 989 family = name 990 self.svg_font_family = f'"{family}"' 991 if family_generic is not None: 992 self.font_family_generic = family_generic 993 self.font_pitch = pitch
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
58class Styles(XmlPart): 59 def _get_style_contexts( 60 self, family: str, automatic: bool = False 61 ) -> list[Element]: 62 if automatic: 63 return [self.get_element("//office:automatic-styles")] 64 if not family: 65 # All possibilities 66 return [ 67 self.get_element("//office:automatic-styles"), 68 self.get_element("//office:styles"), 69 self.get_element("//office:master-styles"), 70 self.get_element("//office:font-face-decls"), 71 ] 72 queries = CONTEXT_MAPPING.get(family) 73 if queries is None: 74 raise ValueError(f"unknown family: {family}") 75 # print('q:', queries) 76 return [self.get_element(query) for query in queries] 77 78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result 100 101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None 137 138 def get_master_pages(self) -> list[Element]: 139 query = make_xpath_query("descendant::style:master-page") 140 return self.get_elements(query) # type:ignore 141 142 def get_master_page(self, position: int = 0) -> Element | None: 143 results = self.get_master_pages() 144 try: 145 return results[position] 146 except IndexError: 147 return None
Representation of an XML part.
Abstraction of the XML library behind.
78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result
Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.
Arguments:
family -- str
automatic -- bool
Return: list of Style
101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name_or_element -- str, odf_style or None
display_name -- str or None
Return: odf_style or None if not found
Inherited Members
168class TOC(Element): 169 """Table of content. 170 The "text:table-of-content" element represents a table of contents for a 171 document. The items that can be listed in a table of contents are: 172 - Headings (as defined by the outline structure of the document), up to 173 a selected level. 174 - Table of contents index marks. 175 - Paragraphs formatted with specified paragraph styles. 176 177 178 Implementation: 179 Default parameters are what most people use: protected from manual 180 modifications and not limited in title levels. 181 182 The name is mandatory and derived automatically from the title if not 183 given. Provide one in case of a conflict with other TOCs in the same 184 document. 185 186 The "text:table-of-content" element has the following attributes: 187 text:name, text:protected, text:protection-key, 188 text:protection-key-digest-algorithm, text:style-name and xml:id. 189 190 Arguments: 191 192 title -- str 193 194 name -- str 195 196 protected -- bool 197 198 outline_level -- int 199 200 style -- str 201 202 title_style -- str 203 204 entry_style -- str 205 """ 206 207 _tag = "text:table-of-content" 208 _properties = ( 209 PropDef("name", "text:name"), 210 PropDef("style", "text:style-name"), 211 PropDef("xml_id", "xml:id"), 212 PropDef("protected", "text:protected"), 213 PropDef("protection_key", "text:protection-key"), 214 PropDef( 215 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 216 ), 217 ) 218 219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style) 247 248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source 270 271 def __str__(self) -> str: 272 return self.get_formatted_text() 273 274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result) 292 293 @property 294 def outline_level(self) -> int | None: 295 source = self.get_element("text:table-of-content-source") 296 if source is None: 297 return None 298 return source.get_attribute_integer("text:outline-level") 299 300 @outline_level.setter 301 def outline_level(self, level: int) -> None: 302 source = self.get_element("text:table-of-content-source") 303 if source is None: 304 source = Element.from_tag("text:table-of-content-source") 305 self.insert(source, FIRST_CHILD) 306 source.set_attribute("text:outline-level", str(level)) 307 308 @property 309 def body(self) -> Element | None: 310 return self.get_element("text:index-body") 311 312 @body.setter 313 def body(self, body: Element | None = None) -> Element | None: 314 old_body = self.body 315 if old_body is not None: 316 self.delete(old_body) 317 if body is None: 318 body = Element.from_tag("text:index-body") 319 self.append(body) 320 return body 321 322 def get_title(self) -> str: 323 index_body = self.body 324 if index_body is None: 325 return "" 326 index_title = index_body.get_element(IndexTitle._tag) 327 if index_title is None: 328 return "" 329 return index_title.text_content 330 331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title 358 359 @staticmethod 360 def _header_numbering(level_indexes: dict[int, int], level: int) -> str: 361 """Return the header hierarchical number (like "1.2.3.").""" 362 numbers: list[int] = [] 363 # before header level 364 for idx in range(1, level): 365 numbers.append(level_indexes.setdefault(idx, 1)) 366 # header level 367 index = level_indexes.get(level, 0) + 1 368 level_indexes[level] = index 369 numbers.append(index) 370 # after header level 371 idx = level + 1 372 while idx in level_indexes: 373 del level_indexes[idx] 374 idx += 1 375 return ".".join(str(x) for x in numbers) + "." 376 377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:
- Headings (as defined by the outline structure of the document), up to a selected level.
- Table of contents index marks.
- Paragraphs formatted with specified paragraph styles.
Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.
The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.
The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.
Arguments:
title -- str
name -- str
protected -- bool
outline_level -- int
style -- str
title_style -- str
entry_style -- str
219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style)
248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source
274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result)
This function should return a beautiful version of the text.
331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title
377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.
For having a pretty TOC, let use_default_styles by default.
Arguments:
document -- Document
use_default_styles -- bool
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class Tab(Element): 195 """This element represents the [UNICODE] tab character (HORIZONTAL 196 TABULATION, U+0009). 197 198 The position attribute contains the number of the tab-stop to which 199 a tab character refers. The position 0 marks the start margin of a 200 paragraph. Note: The position attribute is only a hint to help non-layout 201 oriented consumers to determine the tab/tab-stop association. Layout 202 oriented consumers should determine the tab positions based on the style 203 information 204 """ 205 206 _tag = "text:tab" 207 _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),) 208 209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).
The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information
209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
Arguments:
position -- int
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
94class TabStopStyle(Element): 95 """ODF "style:tab-stop" 96 Base style for a TOC entryBase style for a TOC entry 97 """ 98 99 _tag = "style:tab-stop" 100 _properties = ( 101 PropDef("style_char", "style:char"), 102 PropDef("leader_color", "style:leader-color"), 103 PropDef("leader_style", "style:leader-style"), 104 PropDef("leader_text", "style:leader-text"), 105 PropDef("leader_text_style", "style:leader-text-style"), 106 PropDef("leader_type", "style:leader-type"), 107 PropDef("leader_width", "style:leader-width"), 108 PropDef("style_position", "style:position"), 109 PropDef("style_type", "style:type"), 110 ) 111 112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry
112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
284class Table(Element): 285 """ODF table "table:table" """ 286 287 _tag = "table:table" 288 _caching = True 289 _append = Element.append 290 291 def __init__( 292 self, 293 name: str | None = None, 294 width: int | None = None, 295 height: int | None = None, 296 protected: bool = False, 297 protection_key: str | None = None, 298 display: bool = True, 299 printable: bool = True, 300 print_ranges: list[str] | None = None, 301 style: str | None = None, 302 **kwargs: Any, 303 ) -> None: 304 """Create a table element, optionally prefilled with "height" rows of 305 "width" cells each. 306 307 If the table is to be protected, a protection key must be provided, 308 i.e. a hash value of the password. 309 310 If the table must not be displayed, set "display" to False. 311 312 If the table must not be printed, set "printable" to False. The table 313 will not be printed when it is not displayed, whatever the value of 314 this argument. 315 316 Ranges of cells to print can be provided as a list of cell ranges, 317 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 318 "E6:K12 P6:R12". 319 320 You can access and modify the XML tree manually, but you probably want 321 to use the API to access and alter cells. It will save you from 322 handling repetitions and the same number of cells for each row. 323 324 If you use both the table API and the XML API, you are on your own for 325 ensuiring model integrity. 326 327 Arguments: 328 329 name -- str 330 331 width -- int 332 333 height -- int 334 335 protected -- bool 336 337 protection_key -- str 338 339 display -- bool 340 341 printable -- bool 342 343 print_ranges -- list 344 345 style -- str 346 """ 347 super().__init__(**kwargs) 348 self._indexes = {} 349 self._indexes["_cmap"] = {} 350 self._indexes["_tmap"] = {} 351 if self._do_init: 352 self.name = name 353 if protected: 354 self.protected = protected 355 self.set_protection_key = protection_key 356 if not display: 357 self.displayed = display 358 if not printable: 359 self.printable = printable 360 if print_ranges: 361 self.print_ranges = print_ranges 362 if style: 363 self.style = style 364 # Prefill the table 365 if width is not None or height is not None: 366 width = width or 1 367 height = height or 1 368 # Column groups for style information 369 columns = Column(repeated=width) 370 self._append(columns) 371 for _i in range(height): 372 row = Row(width) 373 self._append(row) 374 self._compute_table_cache() 375 376 def __str__(self) -> str: 377 def write_content(csv_writer: object) -> None: 378 for values in self.iter_values(): 379 line = [] 380 for value in values: 381 if value is None: 382 value = "" 383 if isinstance(value, str): 384 value = value.strip() 385 line.append(value) 386 csv_writer.writerow(line) # type: ignore 387 388 out = StringIO(newline=os.linesep) 389 csv_writer = csv.writer( 390 out, 391 delimiter=" ", 392 doublequote=False, 393 escapechar="\\", 394 lineterminator=os.linesep, 395 quotechar='"', 396 quoting=csv.QUOTE_NONNUMERIC, 397 ) 398 write_content(csv_writer) 399 return out.getvalue() 400 401 def _translate_y_from_any(self, y: str | int) -> int: 402 # "3" (couting from 1) -> 2 (couting from 0) 403 return translate_from_any(y, self.height, 1) 404 405 def _translate_table_coordinates_list( 406 self, 407 coord: tuple | list, 408 ) -> tuple[int | None, ...]: 409 height = self.height 410 width = self.width 411 # assuming we got int values 412 if len(coord) == 1: 413 # It is a row 414 y = coord[0] 415 if y and y < 0: 416 y = increment(y, height) 417 return (None, y, None, y) 418 if len(coord) == 2: 419 # It is a row range, not a cell, because context is table 420 y = coord[0] 421 if y and y < 0: 422 y = increment(y, height) 423 t = coord[1] 424 if t and t < 0: 425 t = increment(t, height) 426 return (None, y, None, t) 427 # should be 4 int 428 x, y, z, t = coord 429 if x and x < 0: 430 x = increment(x, width) 431 if y and y < 0: 432 y = increment(y, height) 433 if z and z < 0: 434 z = increment(z, width) 435 if t and t < 0: 436 t = increment(t, height) 437 return (x, y, z, t) 438 439 def _translate_table_coordinates_str( 440 self, 441 coord_str: str, 442 ) -> tuple[int | None, ...]: 443 height = self.height 444 width = self.width 445 coord = convert_coordinates(coord_str) 446 if len(coord) == 2: 447 x, y = coord 448 if x and x < 0: 449 x = increment(x, width) 450 if y and y < 0: 451 y = increment(y, height) 452 # extent to an area : 453 return (x, y, x, y) 454 x, y, z, t = coord 455 if x and x < 0: 456 x = increment(x, width) 457 if y and y < 0: 458 y = increment(y, height) 459 if z and z < 0: 460 z = increment(z, width) 461 if t and t < 0: 462 t = increment(t, height) 463 return (x, y, z, t) 464 465 def _translate_table_coordinates( 466 self, 467 coord: tuple | list | str, 468 ) -> tuple[int | None, ...]: 469 if isinstance(coord, str): 470 return self._translate_table_coordinates_str(coord) 471 return self._translate_table_coordinates_list(coord) 472 473 def _translate_column_coordinates_str( 474 self, 475 coord_str: str, 476 ) -> tuple[int | None, ...]: 477 width = self.width 478 height = self.height 479 coord = convert_coordinates(coord_str) 480 if len(coord) == 2: 481 x, y = coord 482 if x and x < 0: 483 x = increment(x, width) 484 if y and y < 0: 485 y = increment(y, height) 486 # extent to an area : 487 return (x, y, x, y) 488 x, y, z, t = coord 489 if x and x < 0: 490 x = increment(x, width) 491 if y and y < 0: 492 y = increment(y, height) 493 if z and z < 0: 494 z = increment(z, width) 495 if t and t < 0: 496 t = increment(t, height) 497 return (x, y, z, t) 498 499 def _translate_column_coordinates_list( 500 self, 501 coord: tuple | list, 502 ) -> tuple[int | None, ...]: 503 width = self.width 504 height = self.height 505 # assuming we got int values 506 if len(coord) == 1: 507 # It is a column 508 x = coord[0] 509 if x and x < 0: 510 x = increment(x, width) 511 return (x, None, x, None) 512 if len(coord) == 2: 513 # It is a column range, not a cell, because context is table 514 x = coord[0] 515 if x and x < 0: 516 x = increment(x, width) 517 z = coord[1] 518 if z and z < 0: 519 z = increment(z, width) 520 return (x, None, z, None) 521 # should be 4 int 522 x, y, z, t = coord 523 if x and x < 0: 524 x = increment(x, width) 525 if y and y < 0: 526 y = increment(y, height) 527 if z and z < 0: 528 z = increment(z, width) 529 if t and t < 0: 530 t = increment(t, height) 531 return (x, y, z, t) 532 533 def _translate_column_coordinates( 534 self, 535 coord: tuple | list | str, 536 ) -> tuple[int | None, ...]: 537 if isinstance(coord, str): 538 return self._translate_column_coordinates_str(coord) 539 return self._translate_column_coordinates_list(coord) 540 541 def _translate_cell_coordinates( 542 self, 543 coord: tuple | list | str, 544 ) -> tuple[int | None, int | None]: 545 # we want an x,y result 546 coord = convert_coordinates(coord) 547 if len(coord) == 2: 548 x, y = coord 549 # If we got an area, take the first cell 550 elif len(coord) == 4: 551 x, y, z, t = coord 552 else: 553 raise ValueError(str(coord)) 554 if x and x < 0: 555 x = increment(x, self.width) 556 if y and y < 0: 557 y = increment(y, self.height) 558 return (x, y) 559 560 def _compute_table_cache(self) -> None: 561 idx_repeated_seq = self.elements_repeated_sequence( 562 _xpath_row, "table:number-rows-repeated" 563 ) 564 self._tmap = make_cache_map(idx_repeated_seq) 565 idx_repeated_seq = self.elements_repeated_sequence( 566 _xpath_column, "table:number-columns-repeated" 567 ) 568 self._cmap = make_cache_map(idx_repeated_seq) 569 570 def _update_width(self, row: Row) -> None: 571 """Synchronize the number of columns if the row is bigger. 572 573 Append, don't insert, not to disturb the current layout. 574 """ 575 diff = row.width - self.width 576 if diff > 0: 577 self.append_column(Column(repeated=diff)) 578 579 def _get_formatted_text_normal(self, context: dict | None) -> str: 580 result = [] 581 for row in self.traverse(): 582 for cell in row.traverse(): 583 value = cell.get_value(try_get_text=False) 584 # None ? 585 if value is None: 586 # Try with get_formatted_text on the elements 587 value = [] 588 for element in cell.children: 589 value.append(element.get_formatted_text(context)) 590 value = "".join(value) 591 else: 592 value = str(value) 593 result.append(value) 594 result.append("\n") 595 result.append("\n") 596 return "".join(result) 597 598 def _get_formatted_text_rst(self, context: dict) -> str: # noqa: C901 599 context["no_img_level"] += 1 600 # Strip the table => We must clone 601 table = self.clone 602 table.rstrip(aggressive=True) # type: ignore 603 604 # Fill the rows 605 rows = [] 606 cols_nb = 0 607 cols_size: dict[int, int] = {} 608 for odf_row in table.traverse(): # type: ignore 609 row = [] 610 for i, cell in enumerate(odf_row.traverse()): 611 value = cell.get_value(try_get_text=False) 612 # None ? 613 if value is None: 614 # Try with get_formatted_text on the elements 615 value = [] 616 for element in cell.children: 617 value.append(element.get_formatted_text(context)) 618 value = "".join(value) 619 else: 620 value = str(value) 621 value = value.strip() 622 # Strip the empty columns 623 if value: 624 cols_nb = max(cols_nb, i + 1) 625 # Compute the size of each columns (at least 2) 626 cols_size[i] = max(cols_size.get(i, 2), len(value)) 627 # Append 628 row.append(value) 629 rows.append(row) 630 631 # Nothing ? 632 if cols_nb == 0: 633 return "" 634 635 # Prevent a crash with empty columns (by example with images) 636 for col, size in cols_size.items(): 637 if size == 0: 638 cols_size[col] = 1 639 640 # Update cols_size 641 LINE_MAX = 100 642 COL_MIN = 16 643 644 free_size = LINE_MAX - (cols_nb - 1) * 3 - 4 645 real_size = sum([cols_size[i] for i in range(cols_nb)]) 646 if real_size > free_size: 647 factor = float(free_size) / real_size 648 649 for i in range(cols_nb): 650 old_size = cols_size[i] 651 652 # The cell is already small 653 if old_size <= COL_MIN: 654 continue 655 656 new_size = int(factor * old_size) 657 658 if new_size < COL_MIN: 659 new_size = COL_MIN 660 cols_size[i] = new_size 661 662 # Convert ! 663 result: list[str] = [""] 664 # Construct the first/last line 665 line: list[str] = [] 666 for i in range(cols_nb): 667 line.append("=" * cols_size[i]) 668 line.append(" ") 669 line_str = "".join(line) 670 671 # Add the lines 672 result.append(line_str) 673 for row in rows: 674 # Wrap the row 675 wrapped_row = [] 676 for i, value in enumerate(row[:cols_nb]): 677 wrapped_value = [] 678 for part in value.split("\n"): 679 # Hack to handle correctly the lists or the directives 680 subsequent_indent = "" 681 part_lstripped = part.lstrip() 682 if part_lstripped.startswith("-") or part_lstripped.startswith( 683 ".." 684 ): 685 subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2) 686 wrapped_part = wrap( 687 part, width=cols_size[i], subsequent_indent=subsequent_indent 688 ) 689 if wrapped_part: 690 wrapped_value.extend(wrapped_part) 691 else: 692 wrapped_value.append("") 693 wrapped_row.append(wrapped_value) 694 695 # Append! 696 for j in range(max([1] + [len(values) for values in wrapped_row])): 697 txt_row: list[str] = [] 698 for i in range(cols_nb): 699 values = wrapped_row[i] if i < len(wrapped_row) else [] 700 701 # An empty cell ? 702 if len(values) - 1 < j or not values[j]: 703 if i == 0 and j == 0: 704 txt_row.append("..") 705 txt_row.append(" " * (cols_size[i] - 1)) 706 else: 707 txt_row.append(" " * (cols_size[i] + 1)) 708 continue 709 710 # Not empty 711 value = values[j] 712 txt_row.append(value) 713 txt_row.append(" " * (cols_size[i] - len(value) + 1)) 714 result.append("".join(txt_row)) 715 716 result.append(line_str) 717 result.append("") 718 result.append("") 719 result_str = "\n".join(result) 720 721 context["no_img_level"] -= 1 722 return result_str 723 724 def _translate_x_from_any(self, x: str | int) -> int: 725 return translate_from_any(x, self.width, 0) 726 727 # 728 # Public API 729 # 730 731 def append(self, something: Element | str) -> None: 732 """Dispatch .append() call to append_row() or append_column().""" 733 if isinstance(something, Row): 734 self.append_row(something) 735 elif isinstance(something, Column): 736 self.append_column(something) 737 else: 738 # probably still an error 739 self._append(something) 740 741 @property 742 def height(self) -> int: 743 """Get the current height of the table. 744 745 Return: int 746 """ 747 try: 748 height = self._tmap[-1] + 1 749 except Exception: 750 height = 0 751 return height 752 753 @property 754 def width(self) -> int: 755 """Get the current width of the table, measured on columns. 756 757 Rows may have different widths, use the Table API to ensure width 758 consistency. 759 760 Return: int 761 """ 762 # Columns are our reference for user expected width 763 764 try: 765 width = self._cmap[-1] + 1 766 except Exception: 767 width = 0 768 769 # columns = self._get_columns() 770 # repeated = self.xpath( 771 # 'table:table-column/@table:number-columns-repeated') 772 # unrepeated = len(columns) - len(repeated) 773 # ws = sum(int(r) for r in repeated) + unrepeated 774 # if w != ws: 775 # print "WARNING ws", ws, "w", w 776 777 return width 778 779 @property 780 def size(self) -> tuple[int, int]: 781 """Shortcut to get the current width and height of the table. 782 783 Return: (int, int) 784 """ 785 return self.width, self.height 786 787 @property 788 def name(self) -> str | None: 789 """Get / set the name of the table.""" 790 return self.get_attribute_string("table:name") 791 792 @name.setter 793 def name(self, name: str) -> None: 794 name = _table_name_check(name) 795 # first, update named ranges 796 # fixme : delete name ranges when deleting table, too. 797 for named_range in self.get_named_ranges(table_name=self.name): 798 named_range.set_table_name(name) 799 self.set_attribute("table:name", name) 800 801 @property 802 def protected(self) -> bool: 803 return bool(self.get_attribute("table:protected")) 804 805 @protected.setter 806 def protected(self, protect: bool) -> None: 807 self.set_attribute("table:protected", protect) 808 809 @property 810 def protection_key(self) -> str | None: 811 return self.get_attribute_string("table:protection-key") 812 813 @protection_key.setter 814 def protection_key(self, key: str) -> None: 815 self.set_attribute("table:protection-key", key) 816 817 @property 818 def displayed(self) -> bool: 819 return bool(self.get_attribute("table:display")) 820 821 @displayed.setter 822 def displayed(self, display: bool) -> None: 823 self.set_attribute("table:display", display) 824 825 @property 826 def printable(self) -> bool: 827 printable = self.get_attribute("table:print") 828 # Default value 829 if printable is None: 830 return True 831 return bool(printable) 832 833 @printable.setter 834 def printable(self, printable: bool) -> None: 835 self.set_attribute("table:print", printable) 836 837 @property 838 def print_ranges(self) -> list[str]: 839 ranges = self.get_attribute_string("table:print-ranges") 840 if isinstance(ranges, str): 841 return ranges.split() 842 return [] 843 844 @print_ranges.setter 845 def print_ranges(self, ranges: list[str] | None) -> None: 846 if isinstance(ranges, (list, tuple)): 847 self.set_attribute("table:print-ranges", " ".join(ranges)) 848 else: 849 self.set_attribute("table:print-ranges", ranges) 850 851 @property 852 def style(self) -> str | None: 853 """Get / set the style of the table 854 855 Return: str 856 """ 857 return self.get_attribute_string("table:style-name") 858 859 @style.setter 860 def style(self, style: str | Element) -> None: 861 self.set_style_attribute("table:style-name", style) 862 863 def get_formatted_text(self, context: dict | None = None) -> str: 864 if context and context["rst_mode"]: 865 return self._get_formatted_text_rst(context) 866 return self._get_formatted_text_normal(context) 867 868 def get_values( 869 self, 870 coord: tuple | list | str | None = None, 871 cell_type: str | None = None, 872 complete: bool = True, 873 get_type: bool = False, 874 flat: bool = False, 875 ) -> list: 876 """Get a matrix of values of the table. 877 878 Filter by coordinates will parse the area defined by the coordinates. 879 880 If 'cell_type' is used and 'complete' is True (default), missing values 881 are replaced by None. 882 Filter by ' cell_type = "all" ' will retrieve cells of any 883 type, aka non empty cells. 884 885 If 'cell_type' is None, complete is always True : with no cell type 886 queried, get_values() returns None for each empty cell, the length 887 each lists is equal to the width of the table. 888 889 If get_type is True, returns tuples (value, ODF type of value), or 890 (None, None) for empty cells with complete True. 891 892 If flat is True, the methods return a single list of all the values. 893 By default, flat is False. 894 895 Arguments: 896 897 coord -- str or tuple of int : coordinates of area 898 899 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 900 'currency', 'percentage' or 'all' 901 902 complete -- boolean 903 904 get_type -- boolean 905 906 Return: list of lists of Python types 907 """ 908 if coord: 909 x, y, z, t = self._translate_table_coordinates(coord) 910 else: 911 x = y = z = t = None 912 data = [] 913 for row in self.traverse(start=y, end=t): 914 if z is None: 915 width = self.width 916 else: 917 width = min(z + 1, self.width) 918 if x is not None: 919 width -= x 920 values = row.get_values( 921 (x, z), 922 cell_type=cell_type, 923 complete=complete, 924 get_type=get_type, 925 ) 926 # complete row to match request width 927 if complete: 928 if get_type: 929 values.extend([(None, None)] * (width - len(values))) 930 else: 931 values.extend([None] * (width - len(values))) 932 if flat: 933 data.extend(values) 934 else: 935 data.append(values) 936 return data 937 938 def iter_values( 939 self, 940 coord: tuple | list | str | None = None, 941 cell_type: str | None = None, 942 complete: bool = True, 943 get_type: bool = False, 944 ) -> Iterator[list]: 945 """Iterate through lines of Python values of the table. 946 947 Filter by coordinates will parse the area defined by the coordinates. 948 949 cell_type, complete, grt_type : see get_values() 950 951 952 953 Arguments: 954 955 coord -- str or tuple of int : coordinates of area 956 957 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 958 'currency', 'percentage' or 'all' 959 960 complete -- boolean 961 962 get_type -- boolean 963 964 Return: iterator of lists 965 """ 966 if coord: 967 x, y, z, t = self._translate_table_coordinates(coord) 968 else: 969 x = y = z = t = None 970 for row in self.traverse(start=y, end=t): 971 if z is None: 972 width = self.width 973 else: 974 width = min(z + 1, self.width) 975 if x is not None: 976 width -= x 977 values = row.get_values( 978 (x, z), 979 cell_type=cell_type, 980 complete=complete, 981 get_type=get_type, 982 ) 983 # complete row to match column width 984 if complete: 985 if get_type: 986 values.extend([(None, None)] * (width - len(values))) 987 else: 988 values.extend([None] * (width - len(values))) 989 yield values 990 991 def set_values( 992 self, 993 values: list, 994 coord: tuple | list | str | None = None, 995 style: str | None = None, 996 cell_type: str | None = None, 997 currency: str | None = None, 998 ) -> None: 999 """Set the value of cells in the table, from the 'coord' position 1000 with values. 1001 1002 'coord' is the coordinate of the upper left cell to be modified by 1003 values. If 'coord' is None, default to the position (0,0) ("A1"). 1004 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1005 area is used as coordinate. 1006 1007 The table is *not* cleared before the operation, to reset the table 1008 before setting values, use table.clear(). 1009 1010 A list of lists is expected, with as many lists as rows, and as many 1011 items in each sublist as cells to be setted. None values in the list 1012 will create empty cells with no cell type (but eventually a style). 1013 1014 Arguments: 1015 1016 coord -- tuple or str 1017 1018 values -- list of lists of python types 1019 1020 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1021 'string' or 'time' 1022 1023 currency -- three-letter str 1024 1025 style -- str 1026 """ 1027 if coord: 1028 x, y = self._translate_cell_coordinates(coord) 1029 else: 1030 x = y = 0 1031 if y is None: 1032 y = 0 1033 if x is None: 1034 x = 0 1035 y -= 1 1036 for row_values in values: 1037 y += 1 1038 if not row_values: 1039 continue 1040 row = self.get_row(y, clone=True) 1041 repeated = row.repeated or 1 1042 if repeated >= 2: 1043 row.repeated = None 1044 row.set_values( 1045 row_values, 1046 start=x, 1047 cell_type=cell_type, 1048 currency=currency, 1049 style=style, 1050 ) 1051 self.set_row(y, row, clone=False) 1052 self._update_width(row) 1053 1054 def rstrip(self, aggressive: bool = False) -> None: 1055 """Remove *in-place* empty rows below and empty cells at the right of 1056 the table. Cells are empty if they contain no value or it evaluates 1057 to False, and no style. 1058 1059 If aggressive is True, empty cells with style are removed too. 1060 1061 Argument: 1062 1063 aggressive -- bool 1064 """ 1065 # Step 1: remove empty rows below the table 1066 for row in reversed(self._get_rows()): 1067 if row.is_empty(aggressive=aggressive): 1068 row.parent.delete(row) # type: ignore 1069 else: 1070 break 1071 # Step 2: rstrip remaining rows 1072 max_width = 0 1073 for row in self._get_rows(): 1074 row.rstrip(aggressive=aggressive) 1075 # keep count of the biggest row 1076 max_width = max(max_width, row.width) 1077 # raz cache of rows 1078 self._indexes["_tmap"] = {} 1079 # Step 3: trim columns to match max_width 1080 columns = self._get_columns() 1081 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1082 if not isinstance(repeated_cols, list): 1083 raise TypeError 1084 unrepeated = len(columns) - len(repeated_cols) 1085 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1086 diff = column_width - max_width 1087 if diff > 0: 1088 for column in reversed(columns): 1089 repeated = column.repeated or 1 1090 repeated = repeated - diff 1091 if repeated > 0: 1092 column.repeated = repeated 1093 break 1094 else: 1095 column.parent.delete(column) 1096 diff = -repeated 1097 if diff == 0: 1098 break 1099 # raz cache of columns 1100 self._indexes["_cmap"] = {} 1101 self._compute_table_cache() 1102 1103 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1104 """Swap *in-place* rows and columns of the table. 1105 1106 If 'coord' is not None, apply transpose only to the area defined by the 1107 coordinates. Beware, if area is not square, some cells mays be over 1108 written during the process. 1109 1110 Arguments: 1111 1112 coord -- str or tuple of int : coordinates of area 1113 1114 start -- int or str 1115 """ 1116 data = [] 1117 if coord is None: 1118 for row in self.traverse(): 1119 data.append(list(row.traverse())) 1120 transposed_data = zip_longest(*data) 1121 self.clear() 1122 # new_rows = [] 1123 for row_cells in transposed_data: 1124 if not isiterable(row_cells): 1125 row_cells = (row_cells,) 1126 row = Row() 1127 row.extend_cells(row_cells) 1128 self.append_row(row, clone=False) 1129 self._compute_table_cache() 1130 else: 1131 x, y, z, t = self._translate_table_coordinates(coord) 1132 if x is None: 1133 x = 0 1134 else: 1135 x = min(x, self.width - 1) 1136 if z is None: 1137 z = self.width - 1 1138 else: 1139 z = min(z, self.width - 1) 1140 if y is None: 1141 y = 0 1142 else: 1143 y = min(y, self.height - 1) 1144 if t is None: 1145 t = self.height - 1 1146 else: 1147 t = min(t, self.height - 1) 1148 for row in self.traverse(start=y, end=t): 1149 data.append(list(row.traverse(start=x, end=z))) 1150 transposed_data = zip_longest(*data) 1151 # clear locally 1152 w = z - x + 1 1153 h = t - y + 1 1154 if w != h: 1155 nones = [[None] * w for i in range(h)] 1156 self.set_values(nones, coord=(x, y, z, t)) 1157 # put transposed 1158 filtered_data: list[tuple[Cell]] = [] 1159 for row_cells in transposed_data: 1160 if isinstance(row_cells, (list, tuple)): 1161 filtered_data.append(row_cells) 1162 else: 1163 filtered_data.append((row_cells,)) 1164 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1165 self._compute_table_cache() 1166 1167 def is_empty(self, aggressive: bool = False) -> bool: 1168 """Return whether every cell in the table has no value or the value 1169 evaluates to False (empty string), and no style. 1170 1171 If aggressive is True, empty cells with style are considered empty. 1172 1173 Arguments: 1174 1175 aggressive -- bool 1176 """ 1177 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows()) 1178 1179 # 1180 # Rows 1181 # 1182 1183 def _get_rows(self) -> list[Row]: 1184 return self.get_elements(_xpath_row) # type: ignore 1185 1186 def traverse( # noqa: C901 1187 self, 1188 start: int | None = None, 1189 end: int | None = None, 1190 ) -> Iterator[Row]: 1191 """Yield as many row elements as expected rows in the table, i.e. 1192 expand repetitions by returning the same row as many times as 1193 necessary. 1194 1195 Arguments: 1196 1197 start -- int 1198 1199 end -- int 1200 1201 Copies are returned, use set_row() to push them back. 1202 """ 1203 idx = -1 1204 before = -1 1205 y = 0 1206 if start is None and end is None: 1207 for juska in self._tmap: 1208 idx += 1 1209 if idx in self._indexes["_tmap"]: 1210 row = self._indexes["_tmap"][idx] 1211 else: 1212 row = self._get_element_idx2(_xpath_row_idx, idx) 1213 self._indexes["_tmap"][idx] = row 1214 repeated = juska - before 1215 before = juska 1216 for _i in range(repeated or 1): 1217 # Return a copy without the now obsolete repetition 1218 row = row.clone 1219 row.y = y 1220 y += 1 1221 if repeated > 1: 1222 row.repeated = None 1223 yield row 1224 else: 1225 if start is None: 1226 start = 0 1227 start = max(0, start) 1228 if end is None: 1229 try: 1230 end = self._tmap[-1] 1231 except Exception: 1232 end = -1 1233 start_map = find_odf_idx(self._tmap, start) 1234 if start_map is None: 1235 return 1236 if start_map > 0: 1237 before = self._tmap[start_map - 1] 1238 idx = start_map - 1 1239 before = start - 1 1240 y = start 1241 for juska in self._tmap[start_map:]: 1242 idx += 1 1243 if idx in self._indexes["_tmap"]: 1244 row = self._indexes["_tmap"][idx] 1245 else: 1246 row = self._get_element_idx2(_xpath_row_idx, idx) 1247 self._indexes["_tmap"][idx] = row 1248 repeated = juska - before 1249 before = juska 1250 for _i in range(repeated or 1): 1251 if y <= end: 1252 row = row.clone 1253 row.y = y 1254 y += 1 1255 if repeated > 1 or (y == start and start > 0): 1256 row.repeated = None 1257 yield row 1258 1259 def get_rows( 1260 self, 1261 coord: tuple | list | str | None = None, 1262 style: str | None = None, 1263 content: str | None = None, 1264 ) -> list[Row]: 1265 """Get the list of rows matching the criteria. 1266 1267 Filter by coordinates will parse the area defined by the coordinates. 1268 1269 Arguments: 1270 1271 coord -- str or tuple of int : coordinates of rows 1272 1273 content -- str regex 1274 1275 style -- str 1276 1277 Return: list of rows 1278 """ 1279 if coord: 1280 _x, y, _z, t = self._translate_table_coordinates(coord) 1281 else: 1282 y = t = None 1283 # fixme : not clones ? 1284 if not content and not style: 1285 return list(self.traverse(start=y, end=t)) 1286 rows = [] 1287 for row in self.traverse(start=y, end=t): 1288 if content and not row.match(content): 1289 continue 1290 if style and style != row.style: 1291 continue 1292 rows.append(row) 1293 return rows 1294 1295 def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row: 1296 if y >= self.height: 1297 if create: 1298 return Row() 1299 raise ValueError("Row not found") 1300 row = self._get_row2_base(y) 1301 if row is None: 1302 raise ValueError("Row not found") 1303 if clone: 1304 return row.clone 1305 return row 1306 1307 def _get_row2_base(self, y: int) -> Row | None: 1308 idx = find_odf_idx(self._tmap, y) 1309 if idx is not None: 1310 if idx in self._indexes["_tmap"]: 1311 row = self._indexes["_tmap"][idx] 1312 else: 1313 row = self._get_element_idx2(_xpath_row_idx, idx) 1314 self._indexes["_tmap"][idx] = row 1315 return row 1316 return None 1317 1318 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1319 """Get the row at the given "y" position. 1320 1321 Position start at 0. So cell A4 is on row 3. 1322 1323 A copy is returned, use set_cell() to push it back. 1324 1325 Arguments: 1326 1327 y -- int or str 1328 1329 Return: Row 1330 """ 1331 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1332 y = self._translate_y_from_any(y) 1333 row = self._get_row2(y, clone=clone, create=create) 1334 if row is None: 1335 raise ValueError("Row not found") 1336 row.y = y 1337 return row 1338 1339 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1340 """Replace the row at the given position with the new one. Repetions of 1341 the old row will be adjusted. 1342 1343 If row is None, a new empty row is created. 1344 1345 Position start at 0. So cell A4 is on row 3. 1346 1347 Arguments: 1348 1349 y -- int or str 1350 1351 row -- Row 1352 1353 returns the row, with updated row.y 1354 """ 1355 if row is None: 1356 row = Row() 1357 repeated = 1 1358 clone = False 1359 else: 1360 repeated = row.repeated or 1 1361 y = self._translate_y_from_any(y) 1362 row.y = y 1363 # Outside the defined table ? 1364 diff = y - self.height 1365 if diff == 0: 1366 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1367 elif diff > 0: 1368 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1369 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1370 else: 1371 # Inside the defined table 1372 row_back = set_item_in_vault( # type: ignore 1373 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1374 ) 1375 # print self.serialize(True) 1376 # Update width if necessary 1377 self._update_width(row_back) 1378 return row_back 1379 1380 def insert_row( 1381 self, y: str | int, row: Row | None = None, clone: bool = True 1382 ) -> Row: 1383 """Insert the row before the given "y" position. If no row is given, 1384 an empty one is created. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 If row is None, a new empty row is created. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 row -- Row 1395 1396 returns the row, with updated row.y 1397 """ 1398 if row is None: 1399 row = Row() 1400 clone = False 1401 y = self._translate_y_from_any(y) 1402 diff = y - self.height 1403 if diff < 0: 1404 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1405 elif diff == 0: 1406 row_back = self.append_row(row, clone=clone) 1407 else: 1408 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1409 row_back = self.append_row(row, clone=clone) 1410 row_back.y = y # type: ignore 1411 # Update width if necessary 1412 self._update_width(row_back) # type: ignore 1413 return row_back # type: ignore 1414 1415 def extend_rows(self, rows: list[Row] | None = None) -> None: 1416 """Append a list of rows at the end of the table. 1417 1418 Arguments: 1419 1420 rows -- list of Row 1421 """ 1422 if rows is None: 1423 rows = [] 1424 self.extend(rows) 1425 self._compute_table_cache() 1426 # Update width if necessary 1427 width = self.width 1428 for row in self.traverse(): 1429 if row.width > width: 1430 width = row.width 1431 diff = width - self.width 1432 if diff > 0: 1433 self.append_column(Column(repeated=diff)) 1434 1435 def append_row( 1436 self, 1437 row: Row | None = None, 1438 clone: bool = True, 1439 _repeated: int | None = None, 1440 ) -> Row: 1441 """Append the row at the end of the table. If no row is given, an 1442 empty one is created. 1443 1444 Position start at 0. So cell A4 is on row 3. 1445 1446 Note the columns are automatically created when the first row is 1447 inserted in an empty table. So better insert a filled row. 1448 1449 Arguments: 1450 1451 row -- Row 1452 1453 _repeated -- (optional), repeated value of the row 1454 1455 returns the row, with updated row.y 1456 """ 1457 if row is None: 1458 row = Row() 1459 _repeated = 1 1460 elif clone: 1461 row = row.clone 1462 # Appending a repeated row accepted 1463 # Do not insert next to the last row because it could be in a group 1464 self._append(row) 1465 if _repeated is None: 1466 _repeated = row.repeated or 1 1467 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1468 row.y = self.height - 1 1469 # Initialize columns 1470 if not self._get_columns(): 1471 repeated = row.width 1472 self.insert(Column(repeated=repeated), position=0) 1473 self._compute_table_cache() 1474 # Update width if necessary 1475 self._update_width(row) 1476 return row 1477 1478 def delete_row(self, y: int | str) -> None: 1479 """Delete the row at the given "y" position. 1480 1481 Position start at 0. So cell A4 is on row 3. 1482 1483 Arguments: 1484 1485 y -- int or str 1486 """ 1487 y = self._translate_y_from_any(y) 1488 # Outside the defined table 1489 if y >= self.height: 1490 return 1491 # Inside the defined table 1492 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap") 1493 1494 def get_row_values( 1495 self, 1496 y: int | str, 1497 cell_type: str | None = None, 1498 complete: bool = True, 1499 get_type: bool = False, 1500 ) -> list: 1501 """Shortcut to get the list of Python values for the cells of the row 1502 at the given "y" position. 1503 1504 Position start at 0. So cell A4 is on row 3. 1505 1506 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1507 type, aka non empty cells. 1508 If cell_type and complete is True, replace missing values by None. 1509 1510 If get_type is True, returns a tuple (value, ODF type of value) 1511 1512 Arguments: 1513 1514 y -- int, str 1515 1516 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1517 'currency', 'percentage' or 'all' 1518 1519 complete -- boolean 1520 1521 get_type -- boolean 1522 1523 Return: list of lists of Python types 1524 """ 1525 values = self.get_row(y, clone=False).get_values( 1526 cell_type=cell_type, complete=complete, get_type=get_type 1527 ) 1528 # complete row to match column width 1529 if complete: 1530 if get_type: 1531 values.extend([(None, None)] * (self.width - len(values))) 1532 else: 1533 values.extend([None] * (self.width - len(values))) 1534 return values 1535 1536 def set_row_values( 1537 self, 1538 y: int | str, 1539 values: list, 1540 cell_type: str | None = None, 1541 currency: str | None = None, 1542 style: str | None = None, 1543 ) -> Row: 1544 """Shortcut to set the values of *all* cells of the row at the given 1545 "y" position. 1546 1547 Position start at 0. So cell A4 is on row 3. 1548 1549 Arguments: 1550 1551 y -- int or str 1552 1553 values -- list of Python types 1554 1555 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1556 'string' or 'time' 1557 1558 currency -- three-letter str 1559 1560 style -- str 1561 1562 returns the row, with updated row.y 1563 """ 1564 row = Row() # needed if clones rows 1565 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1566 return self.set_row(y, row) # needed if clones rows 1567 1568 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1569 """Shortcut to set *all* the cells of the row at the given 1570 "y" position. 1571 1572 Position start at 0. So cell A4 is on row 3. 1573 1574 Arguments: 1575 1576 y -- int or str 1577 1578 cells -- list of Python types 1579 1580 style -- str 1581 1582 returns the row, with updated row.y 1583 """ 1584 if cells is None: 1585 cells = [] 1586 row = Row() # needed if clones rows 1587 row.extend_cells(cells) 1588 return self.set_row(y, row) # needed if clones rows 1589 1590 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1591 """Return wether every cell in the row at the given "y" position has 1592 no value or the value evaluates to False (empty string), and no style. 1593 1594 Position start at 0. So cell A4 is on row 3. 1595 1596 If aggressive is True, empty cells with style are considered empty. 1597 1598 Arguments: 1599 1600 y -- int or str 1601 1602 aggressive -- bool 1603 """ 1604 return self.get_row(y, clone=False).is_empty(aggressive=aggressive) 1605 1606 # 1607 # Cells 1608 # 1609 1610 def get_cells( 1611 self, 1612 coord: tuple | list | str | None = None, 1613 cell_type: str | None = None, 1614 style: str | None = None, 1615 content: str | None = None, 1616 flat: bool = False, 1617 ) -> list: 1618 """Get the cells matching the criteria. If 'coord' is None, 1619 parse the whole table, else parse the area defined by 'coord'. 1620 1621 Filter by cell_type = "all" will retrieve cells of any 1622 type, aka non empty cells. 1623 1624 If flat is True (default is False), the method return a single list 1625 of all the values, else a list of lists of cells. 1626 1627 if cell_type, style and content are None, get_cells() will return 1628 the exact number of cells of the area, including empty cells. 1629 1630 Arguments: 1631 1632 coordinates -- str or tuple of int : coordinates of area 1633 1634 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1635 'currency', 'percentage' or 'all' 1636 1637 content -- str regex 1638 1639 style -- str 1640 1641 flat -- boolean 1642 1643 Return: list of tuples 1644 """ 1645 if coord: 1646 x, y, z, t = self._translate_table_coordinates(coord) 1647 else: 1648 x = y = z = t = None 1649 if flat: 1650 cells: list[Cell] = [] 1651 for row in self.traverse(start=y, end=t): 1652 row_cells = row.get_cells( 1653 coord=(x, z), 1654 cell_type=cell_type, 1655 style=style, 1656 content=content, 1657 ) 1658 cells.extend(row_cells) 1659 return cells 1660 else: 1661 lcells: list[list[Cell]] = [] 1662 for row in self.traverse(start=y, end=t): 1663 row_cells = row.get_cells( 1664 coord=(x, z), 1665 cell_type=cell_type, 1666 style=style, 1667 content=content, 1668 ) 1669 lcells.append(row_cells) 1670 return lcells 1671 1672 def get_cell( 1673 self, 1674 coord: tuple | list | str, 1675 clone: bool = True, 1676 keep_repeated: bool = True, 1677 ) -> Cell: 1678 """Get the cell at the given coordinates. 1679 1680 They are either a 2-uplet of (x, y) starting from 0, or a 1681 human-readable position like "C4". 1682 1683 A copy is returned, use ``set_cell`` to push it back. 1684 1685 Arguments: 1686 1687 coord -- (int, int) or str 1688 1689 Return: Cell 1690 """ 1691 x, y = self._translate_cell_coordinates(coord) 1692 if x is None: 1693 raise ValueError 1694 if y is None: 1695 raise ValueError 1696 # Outside the defined table 1697 if y >= self.height: 1698 cell = Cell() 1699 else: 1700 # Inside the defined table 1701 row = self._get_row2_base(y) 1702 if row is None: 1703 raise ValueError 1704 read_cell = row.get_cell(x, clone=clone) 1705 if read_cell is None: 1706 raise ValueError 1707 cell = read_cell 1708 if not keep_repeated: 1709 repeated = cell.repeated or 1 1710 if repeated >= 2: 1711 cell.repeated = None 1712 cell.x = x 1713 cell.y = y 1714 return cell 1715 1716 def get_value( 1717 self, 1718 coord: tuple | list | str, 1719 get_type: bool = False, 1720 ) -> Any: 1721 """Shortcut to get the Python value of the cell at the given 1722 coordinates. 1723 1724 If get_type is True, returns the tuples (value, ODF type) 1725 1726 coord is either a 2-uplet of (x, y) starting from 0, or a 1727 human-readable position like "C4". If an Area is given, the upper 1728 left position is used as coord. 1729 1730 Arguments: 1731 1732 coord -- (int, int) or str : coordinate 1733 1734 Return: Python type 1735 """ 1736 x, y = self._translate_cell_coordinates(coord) 1737 if x is None: 1738 raise ValueError 1739 if y is None: 1740 raise ValueError 1741 # Outside the defined table 1742 if y >= self.height: 1743 if get_type: 1744 return (None, None) 1745 return None 1746 else: 1747 # Inside the defined table 1748 row = self._get_row2_base(y) 1749 if row is None: 1750 raise ValueError 1751 cell = row._get_cell2_base(x) 1752 if cell is None: 1753 if get_type: 1754 return (None, None) 1755 return None 1756 return cell.get_value(get_type=get_type) 1757 1758 def set_cell( 1759 self, 1760 coord: tuple | list | str, 1761 cell: Cell | None = None, 1762 clone: bool = True, 1763 ) -> Cell: 1764 """Replace a cell of the table at the given coordinates. 1765 1766 They are either a 2-uplet of (x, y) starting from 0, or a 1767 human-readable position like "C4". 1768 1769 Arguments: 1770 1771 coord -- (int, int) or str : coordinate 1772 1773 cell -- Cell 1774 1775 return the cell, with x and y updated 1776 """ 1777 if cell is None: 1778 cell = Cell() 1779 clone = False 1780 x, y = self._translate_cell_coordinates(coord) 1781 if x is None: 1782 raise ValueError 1783 if y is None: 1784 raise ValueError 1785 cell.x = x 1786 cell.y = y 1787 if y >= self.height: 1788 row = Row() 1789 cell_back = row.set_cell(x, cell, clone=clone) 1790 self.set_row(y, row, clone=False) 1791 else: 1792 row_read = self._get_row2_base(y) 1793 if row_read is None: 1794 raise ValueError 1795 row = row_read 1796 row.y = y 1797 repeated = row.repeated or 1 1798 if repeated > 1: 1799 row = row.clone 1800 row.repeated = None 1801 cell_back = row.set_cell(x, cell, clone=clone) 1802 self.set_row(y, row, clone=False) 1803 else: 1804 cell_back = row.set_cell(x, cell, clone=clone) 1805 # Update width if necessary, since we don't use set_row 1806 self._update_width(row) 1807 return cell_back 1808 1809 def set_cells( 1810 self, 1811 cells: list[list[Cell]] | list[tuple[Cell]], 1812 coord: tuple | list | str | None = None, 1813 clone: bool = True, 1814 ) -> None: 1815 """Set the cells in the table, from the 'coord' position. 1816 1817 'coord' is the coordinate of the upper left cell to be modified by 1818 values. If 'coord' is None, default to the position (0,0) ("A1"). 1819 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1820 area is used as coordinate. 1821 1822 The table is *not* cleared before the operation, to reset the table 1823 before setting cells, use table.clear(). 1824 1825 A list of lists is expected, with as many lists as rows to be set, and 1826 as many cell in each sublist as cells to be setted in the row. 1827 1828 Arguments: 1829 1830 cells -- list of list of cells 1831 1832 coord -- tuple or str 1833 1834 values -- list of lists of python types 1835 """ 1836 if coord: 1837 x, y = self._translate_cell_coordinates(coord) 1838 else: 1839 x = y = 0 1840 if y is None: 1841 y = 0 1842 if x is None: 1843 x = 0 1844 y -= 1 1845 for row_cells in cells: 1846 y += 1 1847 if not row_cells: 1848 continue 1849 row = self.get_row(y, clone=True) 1850 repeated = row.repeated or 1 1851 if repeated >= 2: 1852 row.repeated = None 1853 row.set_cells(row_cells, start=x, clone=clone) 1854 self.set_row(y, row, clone=False) 1855 self._update_width(row) 1856 1857 def set_value( 1858 self, 1859 coord: tuple | list | str, 1860 value: Any, 1861 cell_type: str | None = None, 1862 currency: str | None = None, 1863 style: str | None = None, 1864 ) -> None: 1865 """Set the Python value of the cell at the given coordinates. 1866 1867 They are either a 2-uplet of (x, y) starting from 0, or a 1868 human-readable position like "C4". 1869 1870 Arguments: 1871 1872 coord -- (int, int) or str 1873 1874 value -- Python type 1875 1876 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1877 'string' or 'time' 1878 1879 currency -- three-letter str 1880 1881 style -- str 1882 1883 """ 1884 self.set_cell( 1885 coord, 1886 Cell(value, cell_type=cell_type, currency=currency, style=style), 1887 clone=False, 1888 ) 1889 1890 def set_cell_image( 1891 self, 1892 coord: tuple | list | str, 1893 image_frame: Frame, 1894 doc_type: str | None = None, 1895 ) -> None: 1896 """Do all the magic to display an image in the cell at the given 1897 coordinates. 1898 1899 They are either a 2-uplet of (x, y) starting from 0, or a 1900 human-readable position like "C4". 1901 1902 The frame element must contain the expected image position and 1903 dimensions. 1904 1905 DrawImage insertion depends on the document type, so the type must be 1906 provided or the table element must be already attached to a document. 1907 1908 Arguments: 1909 1910 coord -- (int, int) or str 1911 1912 image_frame -- Frame including an image 1913 1914 doc_type -- 'spreadsheet' or 'text' 1915 """ 1916 # Test document type 1917 if doc_type is None: 1918 body = self.document_body 1919 if body is None: 1920 raise ValueError("document type not found") 1921 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1922 body.tag 1923 ) 1924 if doc_type is None: 1925 raise ValueError("document type not supported for images") 1926 # We need the end address of the image 1927 x, y = self._translate_cell_coordinates(coord) 1928 if x is None: 1929 raise ValueError 1930 if y is None: 1931 raise ValueError 1932 cell = self.get_cell((x, y)) 1933 image_frame = image_frame.clone # type: ignore 1934 # Remove any previous paragraph, frame, etc. 1935 for child in cell.children: 1936 cell.delete(child) 1937 # Now it all depends on the document type 1938 if doc_type == "spreadsheet": 1939 image_frame.anchor_type = "char" 1940 # The frame needs end coordinates 1941 width, height = image_frame.size 1942 image_frame.set_attribute("table:end-x", width) 1943 image_frame.set_attribute("table:end-y", height) 1944 # FIXME what happens when the address changes? 1945 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 1946 image_frame.set_attribute("table:end-cell-address", address) 1947 # The frame is directly in the cell 1948 cell.append(image_frame) 1949 elif doc_type == "text": 1950 # The frame must be in a paragraph 1951 cell.set_value("") 1952 paragraph = cell.get_element("text:p") 1953 if paragraph is None: 1954 raise ValueError 1955 paragraph.append(image_frame) 1956 self.set_cell(coord, cell) 1957 1958 def insert_cell( 1959 self, 1960 coord: tuple | list | str, 1961 cell: Cell | None = None, 1962 clone: bool = True, 1963 ) -> Cell: 1964 """Insert the given cell at the given coordinates. If no cell is 1965 given, an empty one is created. 1966 1967 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 1968 human-readable position like "C4". 1969 1970 Cells on the right are shifted. Other rows remain untouched. 1971 1972 Arguments: 1973 1974 coord -- (int, int) or str 1975 1976 cell -- Cell 1977 1978 returns the cell with x and y updated 1979 """ 1980 if cell is None: 1981 cell = Cell() 1982 clone = False 1983 if clone: 1984 cell = cell.clone 1985 x, y = self._translate_cell_coordinates(coord) 1986 if x is None: 1987 raise ValueError 1988 if y is None: 1989 raise ValueError 1990 row = self._get_row2(y, clone=True) 1991 row.y = y 1992 row.repeated = None 1993 cell_back = row.insert_cell(x, cell, clone=False) 1994 self.set_row(y, row, clone=False) 1995 # Update width if necessary 1996 self._update_width(row) 1997 return cell_back 1998 1999 def append_cell( 2000 self, 2001 y: int | str, 2002 cell: Cell | None = None, 2003 clone: bool = True, 2004 ) -> Cell: 2005 """Append the given cell at the "y" coordinate. Repeated cells are 2006 accepted. If no cell is given, an empty one is created. 2007 2008 Position start at 0. So cell A4 is on row 3. 2009 2010 Other rows remain untouched. 2011 2012 Arguments: 2013 2014 y -- int or str 2015 2016 cell -- Cell 2017 2018 returns the cell with x and y updated 2019 """ 2020 if cell is None: 2021 cell = Cell() 2022 clone = False 2023 if clone: 2024 cell = cell.clone 2025 y = self._translate_y_from_any(y) 2026 row = self._get_row2(y) 2027 row.y = y 2028 cell_back = row.append_cell(cell, clone=False) 2029 self.set_row(y, row) 2030 # Update width if necessary 2031 self._update_width(row) 2032 return cell_back 2033 2034 def delete_cell(self, coord: tuple | list | str) -> None: 2035 """Delete the cell at the given coordinates, so that next cells are 2036 shifted to the left. 2037 2038 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2039 human-readable position like "C4". 2040 2041 Use set_value() for erasing value. 2042 2043 Arguments: 2044 2045 coord -- (int, int) or str 2046 """ 2047 x, y = self._translate_cell_coordinates(coord) 2048 if x is None: 2049 raise ValueError 2050 if y is None: 2051 raise ValueError 2052 # Outside the defined table 2053 if y >= self.height: 2054 return 2055 # Inside the defined table 2056 row = self._get_row2_base(y) 2057 if row is None: 2058 raise ValueError 2059 row.delete_cell(x) 2060 # self.set_row(y, row) 2061 2062 # Columns 2063 2064 def _get_columns(self) -> list: 2065 return self.get_elements(_xpath_column) 2066 2067 def traverse_columns( # noqa: C901 2068 self, 2069 start: int | None = None, 2070 end: int | None = None, 2071 ) -> Iterator[Column]: 2072 """Yield as many column elements as expected columns in the table, 2073 i.e. expand repetitions by returning the same column as many times as 2074 necessary. 2075 2076 Arguments: 2077 2078 start -- int 2079 2080 end -- int 2081 2082 Copies are returned, use set_column() to push them back. 2083 """ 2084 idx = -1 2085 before = -1 2086 x = 0 2087 if start is None and end is None: 2088 for juska in self._cmap: 2089 idx += 1 2090 if idx in self._indexes["_cmap"]: 2091 column = self._indexes["_cmap"][idx] 2092 else: 2093 column = self._get_element_idx2(_xpath_column_idx, idx) 2094 self._indexes["_cmap"][idx] = column 2095 repeated = juska - before 2096 before = juska 2097 for _i in range(repeated or 1): 2098 # Return a copy without the now obsolete repetition 2099 column = column.clone 2100 column.x = x 2101 x += 1 2102 if repeated > 1: 2103 column.repeated = None 2104 yield column 2105 else: 2106 if start is None: 2107 start = 0 2108 start = max(0, start) 2109 if end is None: 2110 try: 2111 end = self._cmap[-1] 2112 except Exception: 2113 end = -1 2114 start_map = find_odf_idx(self._cmap, start) 2115 if start_map is None: 2116 return 2117 if start_map > 0: 2118 before = self._cmap[start_map - 1] 2119 idx = start_map - 1 2120 before = start - 1 2121 x = start 2122 for juska in self._cmap[start_map:]: 2123 idx += 1 2124 if idx in self._indexes["_cmap"]: 2125 column = self._indexes["_cmap"][idx] 2126 else: 2127 column = self._get_element_idx2(_xpath_column_idx, idx) 2128 self._indexes["_cmap"][idx] = column 2129 repeated = juska - before 2130 before = juska 2131 for _i in range(repeated or 1): 2132 if x <= end: 2133 column = column.clone 2134 column.x = x 2135 x += 1 2136 if repeated > 1 or (x == start and start > 0): 2137 column.repeated = None 2138 yield column 2139 2140 def get_columns( 2141 self, 2142 coord: tuple | list | str | None = None, 2143 style: str | None = None, 2144 ) -> list[Column]: 2145 """Get the list of columns matching the criteria. Each result is a 2146 tuple of (x, column). 2147 2148 Arguments: 2149 2150 coord -- str or tuple of int : coordinates of columns 2151 2152 style -- str 2153 2154 Return: list of columns 2155 """ 2156 if coord: 2157 x, _y, _z, t = self._translate_column_coordinates(coord) 2158 else: 2159 x = t = None 2160 if not style: 2161 return list(self.traverse_columns(start=x, end=t)) 2162 columns = [] 2163 for column in self.traverse_columns(start=x, end=t): 2164 if style != column.style: 2165 continue 2166 columns.append(column) 2167 return columns 2168 2169 def _get_column2(self, x: int) -> Column | None: 2170 # Outside the defined table 2171 if x >= self.width: 2172 return Column() 2173 # Inside the defined table 2174 odf_idx = find_odf_idx(self._cmap, x) 2175 if odf_idx is not None: 2176 column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2177 if column is None: 2178 return None 2179 # fixme : no clone here => change doc and unit tests 2180 return column.clone # type: ignore 2181 # return row 2182 return None 2183 2184 def get_column(self, x: int | str) -> Column: 2185 """Get the column at the given "x" position. 2186 2187 ODF columns don't contain cells, only style information. 2188 2189 Position start at 0. So cell C4 is on column 2. Alphabetical position 2190 like "C" is accepted. 2191 2192 A copy is returned, use set_column() to push it back. 2193 2194 Arguments: 2195 2196 x -- int or str 2197 2198 Return: Column 2199 """ 2200 x = self._translate_x_from_any(x) 2201 column = self._get_column2(x) 2202 if column is None: 2203 raise ValueError 2204 column.x = x 2205 return column 2206 2207 def set_column( 2208 self, 2209 x: int | str, 2210 column: Column | None = None, 2211 ) -> Column: 2212 """Replace the column at the given "x" position. 2213 2214 ODF columns don't contain cells, only style information. 2215 2216 Position start at 0. So cell C4 is on column 2. Alphabetical position 2217 like "C" is accepted. 2218 2219 Arguments: 2220 2221 x -- int or str 2222 2223 column -- Column 2224 """ 2225 x = self._translate_x_from_any(x) 2226 if column is None: 2227 column = Column() 2228 repeated = 1 2229 else: 2230 repeated = column.repeated or 1 2231 column.x = x 2232 # Outside the defined table ? 2233 diff = x - self.width 2234 if diff == 0: 2235 column_back = self.append_column(column, _repeated=repeated) 2236 elif diff > 0: 2237 self.append_column(Column(repeated=diff), _repeated=diff) 2238 column_back = self.append_column(column, _repeated=repeated) 2239 else: 2240 # Inside the defined table 2241 column_back = set_item_in_vault( # type: ignore 2242 x, column, self, _xpath_column_idx, "_cmap" 2243 ) 2244 return column_back 2245 2246 def insert_column( 2247 self, 2248 x: int | str, 2249 column: Column | None = None, 2250 ) -> Column: 2251 """Insert the column before the given "x" position. If no column is 2252 given, an empty one is created. 2253 2254 ODF columns don't contain cells, only style information. 2255 2256 Position start at 0. So cell C4 is on column 2. Alphabetical position 2257 like "C" is accepted. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 column -- Column 2264 """ 2265 if column is None: 2266 column = Column() 2267 x = self._translate_x_from_any(x) 2268 diff = x - self.width 2269 if diff < 0: 2270 column_back = insert_item_in_vault( 2271 x, column, self, _xpath_column_idx, "_cmap" 2272 ) 2273 elif diff == 0: 2274 column_back = self.append_column(column.clone) 2275 else: 2276 self.append_column(Column(repeated=diff), _repeated=diff) 2277 column_back = self.append_column(column.clone) 2278 column_back.x = x # type: ignore 2279 # Repetitions are accepted 2280 repeated = column.repeated or 1 2281 # Update width on every row 2282 for row in self._get_rows(): 2283 if row.width > x: 2284 row.insert_cell(x, Cell(repeated=repeated)) 2285 # Shorter rows don't need insert 2286 # Longer rows shouldn't exist! 2287 return column_back # type: ignore 2288 2289 def append_column( 2290 self, 2291 column: Column | None = None, 2292 _repeated: int | None = None, 2293 ) -> Column: 2294 """Append the column at the end of the table. If no column is given, 2295 an empty one is created. 2296 2297 ODF columns don't contain cells, only style information. 2298 2299 Position start at 0. So cell C4 is on column 2. Alphabetical position 2300 like "C" is accepted. 2301 2302 Arguments: 2303 2304 column -- Column 2305 """ 2306 if column is None: 2307 column = Column() 2308 else: 2309 column = column.clone 2310 if not self._cmap: 2311 position = 0 2312 else: 2313 odf_idx = len(self._cmap) - 1 2314 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2315 if last_column is None: 2316 raise ValueError 2317 position = self.index(last_column) + 1 2318 column.x = self.width 2319 self.insert(column, position=position) 2320 # Repetitions are accepted 2321 if _repeated is None: 2322 _repeated = column.repeated or 1 2323 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2324 # No need to update row widths 2325 return column 2326 2327 def delete_column(self, x: int | str) -> None: 2328 """Delete the column at the given position. ODF columns don't contain 2329 cells, only style information. 2330 2331 Position start at 0. So cell C4 is on column 2. Alphabetical position 2332 like "C" is accepted. 2333 2334 Arguments: 2335 2336 x -- int or str 2337 """ 2338 x = self._translate_x_from_any(x) 2339 # Outside the defined table 2340 if x >= self.width: 2341 return 2342 # Inside the defined table 2343 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2344 # Update width 2345 width = self.width 2346 for row in self._get_rows(): 2347 if row.width >= width: 2348 row.delete_cell(x) 2349 2350 def get_column_cells( # noqa: C901 2351 self, 2352 x: int | str, 2353 style: str | None = None, 2354 content: str | None = None, 2355 cell_type: str | None = None, 2356 complete: bool = False, 2357 ) -> list[Cell | None]: 2358 """Get the list of cells at the given position. 2359 2360 Position start at 0. So cell C4 is on column 2. Alphabetical position 2361 like "C" is accepted. 2362 2363 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2364 type, aka non empty cells. 2365 2366 If complete is True, replace missing values by None. 2367 2368 Arguments: 2369 2370 x -- int or str 2371 2372 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2373 'currency', 'percentage' or 'all' 2374 2375 content -- str regex 2376 2377 style -- str 2378 2379 complete -- boolean 2380 2381 Return: list of Cell 2382 """ 2383 x = self._translate_x_from_any(x) 2384 if cell_type: 2385 cell_type = cell_type.lower().strip() 2386 cells: list[Cell | None] = [] 2387 if not style and not content and not cell_type: 2388 for row in self.traverse(): 2389 cells.append(row.get_cell(x, clone=True)) 2390 return cells 2391 for row in self.traverse(): 2392 cell = row.get_cell(x, clone=True) 2393 if cell is None: 2394 raise ValueError 2395 # Filter the cells by cell_type 2396 if cell_type: 2397 ctype = cell.type 2398 if not ctype or not (ctype == cell_type or cell_type == "all"): 2399 if complete: 2400 cells.append(None) 2401 continue 2402 # Filter the cells with the regex 2403 if content and not cell.match(content): 2404 if complete: 2405 cells.append(None) 2406 continue 2407 # Filter the cells with the style 2408 if style and style != cell.style: 2409 if complete: 2410 cells.append(None) 2411 continue 2412 cells.append(cell) 2413 return cells 2414 2415 def get_column_values( 2416 self, 2417 x: int | str, 2418 cell_type: str | None = None, 2419 complete: bool = True, 2420 get_type: bool = False, 2421 ) -> list[Any]: 2422 """Shortcut to get the list of Python values for the cells at the 2423 given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 If cell_type and complete is True, replace missing values by None. 2431 2432 If get_type is True, returns a tuple (value, ODF type of value) 2433 2434 Arguments: 2435 2436 x -- int or str 2437 2438 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2439 'currency', 'percentage' or 'all' 2440 2441 complete -- boolean 2442 2443 get_type -- boolean 2444 2445 Return: list of Python types 2446 """ 2447 cells = self.get_column_cells( 2448 x, style=None, content=None, cell_type=cell_type, complete=complete 2449 ) 2450 values: list[Any] = [] 2451 for cell in cells: 2452 if cell is None: 2453 if complete: 2454 if get_type: 2455 values.append((None, None)) 2456 else: 2457 values.append(None) 2458 continue 2459 if cell_type: 2460 ctype = cell.type 2461 if not ctype or not (ctype == cell_type or cell_type == "all"): 2462 if complete: 2463 if get_type: 2464 values.append((None, None)) 2465 else: 2466 values.append(None) 2467 continue 2468 values.append(cell.get_value(get_type=get_type)) 2469 return values 2470 2471 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2472 """Shortcut to set the list of cells at the given position. 2473 2474 Position start at 0. So cell C4 is on column 2. Alphabetical position 2475 like "C" is accepted. 2476 2477 The list must have the same length than the table height. 2478 2479 Arguments: 2480 2481 x -- int or str 2482 2483 cells -- list of Cell 2484 """ 2485 height = self.height 2486 if len(cells) != height: 2487 raise ValueError(f"col mismatch: {height} cells expected") 2488 cells_iterator = iter(cells) 2489 for y, row in enumerate(self.traverse()): 2490 row.set_cell(x, next(cells_iterator)) 2491 self.set_row(y, row) 2492 2493 def set_column_values( 2494 self, 2495 x: int | str, 2496 values: list, 2497 cell_type: str | None = None, 2498 currency: str | None = None, 2499 style: str | None = None, 2500 ) -> None: 2501 """Shortcut to set the list of Python values of cells at the given 2502 position. 2503 2504 Position start at 0. So cell C4 is on column 2. Alphabetical position 2505 like "C" is accepted. 2506 2507 The list must have the same length than the table height. 2508 2509 Arguments: 2510 2511 x -- int or str 2512 2513 values -- list of Python types 2514 2515 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2516 'string' or 'time' 2517 2518 currency -- three-letter str 2519 2520 style -- str 2521 """ 2522 cells = [ 2523 Cell(value, cell_type=cell_type, currency=currency, style=style) 2524 for value in values 2525 ] 2526 self.set_column_cells(x, cells) 2527 2528 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2529 """Return wether every cell in the column at "x" position has no value 2530 or the value evaluates to False (empty string), and no style. 2531 2532 Position start at 0. So cell C4 is on column 2. Alphabetical position 2533 like "C" is accepted. 2534 2535 If aggressive is True, empty cells with style are considered empty. 2536 2537 Return: bool 2538 """ 2539 for cell in self.get_column_cells(x): 2540 if cell is None: 2541 continue 2542 if not cell.is_empty(aggressive=aggressive): 2543 return False 2544 return True 2545 2546 # Named Range 2547 2548 def get_named_ranges( # type: ignore 2549 self, 2550 table_name: str | list[str] | None = None, 2551 ) -> list[NamedRange]: 2552 """Returns the list of available Name Ranges of the spreadsheet. If 2553 table_name is provided, limits the search to these tables. 2554 Beware : named ranges are stored at the body level, thus do not call 2555 this method on a cloned table. 2556 2557 Arguments: 2558 2559 table_names -- str or list of str, names of tables 2560 2561 Return : list of table_range 2562 """ 2563 body = self.document_body 2564 if not body: 2565 return [] 2566 all_named_ranges = body.get_named_ranges() 2567 if not table_name: 2568 return all_named_ranges # type:ignore 2569 filter_ = [] 2570 if isinstance(table_name, str): 2571 filter_.append(table_name) 2572 elif isiterable(table_name): 2573 filter_.extend(table_name) 2574 else: 2575 raise ValueError( 2576 f"table_name must be string or Iterable, not {type(table_name)}" 2577 ) 2578 return [ 2579 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2580 ] 2581 2582 def get_named_range(self, name: str) -> NamedRange: 2583 """Returns the Name Ranges of the specified name. If 2584 table_name is provided, limits the search to these tables. 2585 Beware : named ranges are stored at the body level, thus do not call 2586 this method on a cloned table. 2587 2588 Arguments: 2589 2590 name -- str, name of the named range object 2591 2592 Return : NamedRange 2593 """ 2594 body = self.document_body 2595 if not body: 2596 raise ValueError("Table is not inside a document") 2597 return body.get_named_range(name) # type: ignore 2598 2599 def set_named_range( 2600 self, 2601 name: str, 2602 crange: str | tuple | list, 2603 table_name: str | None = None, 2604 usage: str | None = None, 2605 ) -> None: 2606 """Create a Named Range element and insert it in the document. 2607 Beware : named ranges are stored at the body level, thus do not call 2608 this method on a cloned table. 2609 2610 Arguments: 2611 2612 name -- str, name of the named range 2613 2614 crange -- str or tuple of int, cell or area coordinate 2615 2616 table_name -- str, name of the table 2617 2618 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2619 """ 2620 body = self.document_body 2621 if not body: 2622 raise ValueError("Table is not inside a document") 2623 if not name: 2624 raise ValueError("Name required.") 2625 if table_name is None: 2626 table_name = self.name 2627 named_range = NamedRange(name, crange, table_name, usage) 2628 body.append_named_range(named_range) 2629 2630 def delete_named_range(self, name: str) -> None: 2631 """Delete the Named Range of specified name from the spreadsheet. 2632 Beware : named ranges are stored at the body level, thus do not call 2633 this method on a cloned table. 2634 2635 Arguments: 2636 2637 name -- str 2638 """ 2639 name = name.strip() 2640 if not name: 2641 raise ValueError("Name required.") 2642 body = self.document_body 2643 if not body: 2644 raise ValueError("Table is not inside a document.") 2645 body.delete_named_range(name) 2646 2647 # 2648 # Cell span 2649 # 2650 2651 def set_span( # noqa: C901 2652 self, 2653 area: str | tuple | list, 2654 merge: bool = False, 2655 ) -> bool: 2656 """Create a Cell Span : span the first cell of the area on several 2657 columns and/or rows. 2658 If merge is True, replace text of the cell by the concatenation of 2659 existing text in covered cells. 2660 Beware : if merge is True, old text is changed, if merge is False 2661 (the default), old text in coverd cells is still present but not 2662 displayed by most GUI. 2663 2664 If the area defines only one cell, the set span will do nothing. 2665 It is not allowed to apply set span to an area whose one cell already 2666 belongs to previous cell span. 2667 2668 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2669 be provided as an alpha numeric value like "A1:B2' or a tuple like 2670 (0, 0, 1, 1) or (0, 0). 2671 2672 Arguments: 2673 2674 area -- str or tuple of int, cell or area coordinate 2675 2676 merge -- boolean 2677 """ 2678 # get area 2679 digits = convert_coordinates(area) 2680 if len(digits) == 4: 2681 x, y, z, t = digits 2682 else: 2683 x, y = digits 2684 z, t = digits 2685 start = x, y 2686 end = z, t 2687 if start == end: 2688 # one cell : do nothing 2689 return False 2690 if x is None: 2691 raise ValueError 2692 if y is None: 2693 raise ValueError 2694 if z is None: 2695 raise ValueError 2696 if t is None: 2697 raise ValueError 2698 # check for previous span 2699 good = True 2700 # Check boundaries and empty cells : need to crate non existent cells 2701 # so don't use get_cells directly, but get_cell 2702 cells = [] 2703 for yy in range(y, t + 1): 2704 row_cells = [] 2705 for xx in range(x, z + 1): 2706 row_cells.append( 2707 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2708 ) 2709 cells.append(row_cells) 2710 for row in cells: 2711 for cell in row: 2712 if cell._is_spanned(): 2713 good = False 2714 break 2715 if not good: 2716 break 2717 if not good: 2718 return False 2719 # Check boundaries 2720 # if z >= self.width or t >= self.height: 2721 # self.set_cell(coord = end) 2722 # print area, z, t 2723 # cells = self.get_cells((x, y, z, t)) 2724 # print cells 2725 # do it: 2726 if merge: 2727 val_list = [] 2728 for row in cells: 2729 for cell in row: 2730 if cell.is_empty(aggressive=True): 2731 continue 2732 val = cell.get_value() 2733 if val is not None: 2734 if isinstance(val, str): 2735 val.strip() 2736 if val != "": 2737 val_list.append(val) 2738 cell.clear() 2739 if val_list: 2740 if len(val_list) == 1: 2741 cells[0][0].set_value(val_list[0]) 2742 else: 2743 value = " ".join([str(v) for v in val_list if v]) 2744 cells[0][0].set_value(value) 2745 cols = z - x + 1 2746 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2747 rows = t - y + 1 2748 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2749 for cell in cells[0][1:]: 2750 cell.tag = "table:covered-table-cell" 2751 for row in cells[1:]: 2752 for cell in row: 2753 cell.tag = "table:covered-table-cell" 2754 # replace cells in table 2755 self.set_cells(cells, coord=start, clone=False) 2756 return True 2757 2758 def del_span(self, area: str | tuple | list) -> bool: 2759 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2760 cell of the spanned area. 2761 2762 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2763 be provided as an alpha numeric value like "A1:B2' or a tuple like 2764 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2765 is used. 2766 2767 Arguments: 2768 2769 area -- str or tuple of int, cell or area coordinate 2770 """ 2771 # get area 2772 digits = convert_coordinates(area) 2773 if len(digits) == 4: 2774 x, y, _z, _t = digits 2775 else: 2776 x, y = digits 2777 if x is None: 2778 raise ValueError 2779 if y is None: 2780 raise ValueError 2781 start = x, y 2782 # check for previous span 2783 cell0 = self.get_cell(start) 2784 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2785 if nb_cols is None: 2786 return False 2787 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2788 if nb_rows is None: 2789 return False 2790 z = x + nb_cols - 1 2791 t = y + nb_rows - 1 2792 cells = self.get_cells((x, y, z, t)) 2793 cells[0][0].del_attribute("table:number-columns-spanned") 2794 cells[0][0].del_attribute("table:number-rows-spanned") 2795 for cell in cells[0][1:]: 2796 cell.tag = "table:table-cell" 2797 for row in cells[1:]: 2798 for cell in row: 2799 cell.tag = "table:table-cell" 2800 # replace cells in table 2801 self.set_cells(cells, coord=start, clone=False) 2802 return True 2803 2804 # Utilities 2805 2806 def to_csv( 2807 self, 2808 path_or_file: str | Path | None = None, 2809 dialect: str = "excel", 2810 ) -> Any: 2811 """Write the table as CSV in the file. 2812 2813 If the file is a string, it is opened as a local path. Else an 2814 opened file-like is expected. 2815 2816 Arguments: 2817 2818 path_or_file -- str or file-like 2819 2820 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2821 """ 2822 2823 def write_content(csv_writer: object) -> None: 2824 for values in self.iter_values(): 2825 line = [] 2826 for value in values: 2827 if value is None: 2828 value = "" 2829 if isinstance(value, str): 2830 value = value.strip() 2831 line.append(value) 2832 csv_writer.writerow(line) # type: ignore 2833 2834 out = StringIO(newline="") 2835 csv_writer = csv.writer(out, dialect=dialect) 2836 write_content(csv_writer) 2837 if path_or_file is None: 2838 return out.getvalue() 2839 path = Path(path_or_file) 2840 path.write_text(out.getvalue()) 2841 return None
ODF table "table:table"
291 def __init__( 292 self, 293 name: str | None = None, 294 width: int | None = None, 295 height: int | None = None, 296 protected: bool = False, 297 protection_key: str | None = None, 298 display: bool = True, 299 printable: bool = True, 300 print_ranges: list[str] | None = None, 301 style: str | None = None, 302 **kwargs: Any, 303 ) -> None: 304 """Create a table element, optionally prefilled with "height" rows of 305 "width" cells each. 306 307 If the table is to be protected, a protection key must be provided, 308 i.e. a hash value of the password. 309 310 If the table must not be displayed, set "display" to False. 311 312 If the table must not be printed, set "printable" to False. The table 313 will not be printed when it is not displayed, whatever the value of 314 this argument. 315 316 Ranges of cells to print can be provided as a list of cell ranges, 317 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 318 "E6:K12 P6:R12". 319 320 You can access and modify the XML tree manually, but you probably want 321 to use the API to access and alter cells. It will save you from 322 handling repetitions and the same number of cells for each row. 323 324 If you use both the table API and the XML API, you are on your own for 325 ensuiring model integrity. 326 327 Arguments: 328 329 name -- str 330 331 width -- int 332 333 height -- int 334 335 protected -- bool 336 337 protection_key -- str 338 339 display -- bool 340 341 printable -- bool 342 343 print_ranges -- list 344 345 style -- str 346 """ 347 super().__init__(**kwargs) 348 self._indexes = {} 349 self._indexes["_cmap"] = {} 350 self._indexes["_tmap"] = {} 351 if self._do_init: 352 self.name = name 353 if protected: 354 self.protected = protected 355 self.set_protection_key = protection_key 356 if not display: 357 self.displayed = display 358 if not printable: 359 self.printable = printable 360 if print_ranges: 361 self.print_ranges = print_ranges 362 if style: 363 self.style = style 364 # Prefill the table 365 if width is not None or height is not None: 366 width = width or 1 367 height = height or 1 368 # Column groups for style information 369 columns = Column(repeated=width) 370 self._append(columns) 371 for _i in range(height): 372 row = Row(width) 373 self._append(row) 374 self._compute_table_cache()
Create a table element, optionally prefilled with "height" rows of "width" cells each.
If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.
If the table must not be displayed, set "display" to False.
If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.
Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".
You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.
If you use both the table API and the XML API, you are on your own for ensuiring model integrity.
Arguments:
name -- str
width -- int
height -- int
protected -- bool
protection_key -- str
display -- bool
printable -- bool
print_ranges -- list
style -- str
731 def append(self, something: Element | str) -> None: 732 """Dispatch .append() call to append_row() or append_column().""" 733 if isinstance(something, Row): 734 self.append_row(something) 735 elif isinstance(something, Column): 736 self.append_column(something) 737 else: 738 # probably still an error 739 self._append(something)
Dispatch .append() call to append_row() or append_column().
741 @property 742 def height(self) -> int: 743 """Get the current height of the table. 744 745 Return: int 746 """ 747 try: 748 height = self._tmap[-1] + 1 749 except Exception: 750 height = 0 751 return height
Get the current height of the table.
Return: int
753 @property 754 def width(self) -> int: 755 """Get the current width of the table, measured on columns. 756 757 Rows may have different widths, use the Table API to ensure width 758 consistency. 759 760 Return: int 761 """ 762 # Columns are our reference for user expected width 763 764 try: 765 width = self._cmap[-1] + 1 766 except Exception: 767 width = 0 768 769 # columns = self._get_columns() 770 # repeated = self.xpath( 771 # 'table:table-column/@table:number-columns-repeated') 772 # unrepeated = len(columns) - len(repeated) 773 # ws = sum(int(r) for r in repeated) + unrepeated 774 # if w != ws: 775 # print "WARNING ws", ws, "w", w 776 777 return width
Get the current width of the table, measured on columns.
Rows may have different widths, use the Table API to ensure width consistency.
Return: int
779 @property 780 def size(self) -> tuple[int, int]: 781 """Shortcut to get the current width and height of the table. 782 783 Return: (int, int) 784 """ 785 return self.width, self.height
Shortcut to get the current width and height of the table.
Return: (int, int)
787 @property 788 def name(self) -> str | None: 789 """Get / set the name of the table.""" 790 return self.get_attribute_string("table:name")
Get / set the name of the table.
851 @property 852 def style(self) -> str | None: 853 """Get / set the style of the table 854 855 Return: str 856 """ 857 return self.get_attribute_string("table:style-name")
Get / set the style of the table
Return: str
863 def get_formatted_text(self, context: dict | None = None) -> str: 864 if context and context["rst_mode"]: 865 return self._get_formatted_text_rst(context) 866 return self._get_formatted_text_normal(context)
This function should return a beautiful version of the text.
868 def get_values( 869 self, 870 coord: tuple | list | str | None = None, 871 cell_type: str | None = None, 872 complete: bool = True, 873 get_type: bool = False, 874 flat: bool = False, 875 ) -> list: 876 """Get a matrix of values of the table. 877 878 Filter by coordinates will parse the area defined by the coordinates. 879 880 If 'cell_type' is used and 'complete' is True (default), missing values 881 are replaced by None. 882 Filter by ' cell_type = "all" ' will retrieve cells of any 883 type, aka non empty cells. 884 885 If 'cell_type' is None, complete is always True : with no cell type 886 queried, get_values() returns None for each empty cell, the length 887 each lists is equal to the width of the table. 888 889 If get_type is True, returns tuples (value, ODF type of value), or 890 (None, None) for empty cells with complete True. 891 892 If flat is True, the methods return a single list of all the values. 893 By default, flat is False. 894 895 Arguments: 896 897 coord -- str or tuple of int : coordinates of area 898 899 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 900 'currency', 'percentage' or 'all' 901 902 complete -- boolean 903 904 get_type -- boolean 905 906 Return: list of lists of Python types 907 """ 908 if coord: 909 x, y, z, t = self._translate_table_coordinates(coord) 910 else: 911 x = y = z = t = None 912 data = [] 913 for row in self.traverse(start=y, end=t): 914 if z is None: 915 width = self.width 916 else: 917 width = min(z + 1, self.width) 918 if x is not None: 919 width -= x 920 values = row.get_values( 921 (x, z), 922 cell_type=cell_type, 923 complete=complete, 924 get_type=get_type, 925 ) 926 # complete row to match request width 927 if complete: 928 if get_type: 929 values.extend([(None, None)] * (width - len(values))) 930 else: 931 values.extend([None] * (width - len(values))) 932 if flat: 933 data.extend(values) 934 else: 935 data.append(values) 936 return data
Get a matrix of values of the table.
Filter by coordinates will parse the area defined by the coordinates.
If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.
If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.
If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.
If flat is True, the methods return a single list of all the values. By default, flat is False.
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
938 def iter_values( 939 self, 940 coord: tuple | list | str | None = None, 941 cell_type: str | None = None, 942 complete: bool = True, 943 get_type: bool = False, 944 ) -> Iterator[list]: 945 """Iterate through lines of Python values of the table. 946 947 Filter by coordinates will parse the area defined by the coordinates. 948 949 cell_type, complete, grt_type : see get_values() 950 951 952 953 Arguments: 954 955 coord -- str or tuple of int : coordinates of area 956 957 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 958 'currency', 'percentage' or 'all' 959 960 complete -- boolean 961 962 get_type -- boolean 963 964 Return: iterator of lists 965 """ 966 if coord: 967 x, y, z, t = self._translate_table_coordinates(coord) 968 else: 969 x = y = z = t = None 970 for row in self.traverse(start=y, end=t): 971 if z is None: 972 width = self.width 973 else: 974 width = min(z + 1, self.width) 975 if x is not None: 976 width -= x 977 values = row.get_values( 978 (x, z), 979 cell_type=cell_type, 980 complete=complete, 981 get_type=get_type, 982 ) 983 # complete row to match column width 984 if complete: 985 if get_type: 986 values.extend([(None, None)] * (width - len(values))) 987 else: 988 values.extend([None] * (width - len(values))) 989 yield values
Iterate through lines of Python values of the table.
Filter by coordinates will parse the area defined by the coordinates.
cell_type, complete, grt_type : see get_values()
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: iterator of lists
991 def set_values( 992 self, 993 values: list, 994 coord: tuple | list | str | None = None, 995 style: str | None = None, 996 cell_type: str | None = None, 997 currency: str | None = None, 998 ) -> None: 999 """Set the value of cells in the table, from the 'coord' position 1000 with values. 1001 1002 'coord' is the coordinate of the upper left cell to be modified by 1003 values. If 'coord' is None, default to the position (0,0) ("A1"). 1004 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1005 area is used as coordinate. 1006 1007 The table is *not* cleared before the operation, to reset the table 1008 before setting values, use table.clear(). 1009 1010 A list of lists is expected, with as many lists as rows, and as many 1011 items in each sublist as cells to be setted. None values in the list 1012 will create empty cells with no cell type (but eventually a style). 1013 1014 Arguments: 1015 1016 coord -- tuple or str 1017 1018 values -- list of lists of python types 1019 1020 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1021 'string' or 'time' 1022 1023 currency -- three-letter str 1024 1025 style -- str 1026 """ 1027 if coord: 1028 x, y = self._translate_cell_coordinates(coord) 1029 else: 1030 x = y = 0 1031 if y is None: 1032 y = 0 1033 if x is None: 1034 x = 0 1035 y -= 1 1036 for row_values in values: 1037 y += 1 1038 if not row_values: 1039 continue 1040 row = self.get_row(y, clone=True) 1041 repeated = row.repeated or 1 1042 if repeated >= 2: 1043 row.repeated = None 1044 row.set_values( 1045 row_values, 1046 start=x, 1047 cell_type=cell_type, 1048 currency=currency, 1049 style=style, 1050 ) 1051 self.set_row(y, row, clone=False) 1052 self._update_width(row)
Set the value of cells in the table, from the 'coord' position with values.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting values, use table.clear().
A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).
Arguments:
coord -- tuple or str
values -- list of lists of python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1054 def rstrip(self, aggressive: bool = False) -> None: 1055 """Remove *in-place* empty rows below and empty cells at the right of 1056 the table. Cells are empty if they contain no value or it evaluates 1057 to False, and no style. 1058 1059 If aggressive is True, empty cells with style are removed too. 1060 1061 Argument: 1062 1063 aggressive -- bool 1064 """ 1065 # Step 1: remove empty rows below the table 1066 for row in reversed(self._get_rows()): 1067 if row.is_empty(aggressive=aggressive): 1068 row.parent.delete(row) # type: ignore 1069 else: 1070 break 1071 # Step 2: rstrip remaining rows 1072 max_width = 0 1073 for row in self._get_rows(): 1074 row.rstrip(aggressive=aggressive) 1075 # keep count of the biggest row 1076 max_width = max(max_width, row.width) 1077 # raz cache of rows 1078 self._indexes["_tmap"] = {} 1079 # Step 3: trim columns to match max_width 1080 columns = self._get_columns() 1081 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1082 if not isinstance(repeated_cols, list): 1083 raise TypeError 1084 unrepeated = len(columns) - len(repeated_cols) 1085 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1086 diff = column_width - max_width 1087 if diff > 0: 1088 for column in reversed(columns): 1089 repeated = column.repeated or 1 1090 repeated = repeated - diff 1091 if repeated > 0: 1092 column.repeated = repeated 1093 break 1094 else: 1095 column.parent.delete(column) 1096 diff = -repeated 1097 if diff == 0: 1098 break 1099 # raz cache of columns 1100 self._indexes["_cmap"] = {} 1101 self._compute_table_cache()
Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.
If aggressive is True, empty cells with style are removed too.
Argument:
aggressive -- bool
1103 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1104 """Swap *in-place* rows and columns of the table. 1105 1106 If 'coord' is not None, apply transpose only to the area defined by the 1107 coordinates. Beware, if area is not square, some cells mays be over 1108 written during the process. 1109 1110 Arguments: 1111 1112 coord -- str or tuple of int : coordinates of area 1113 1114 start -- int or str 1115 """ 1116 data = [] 1117 if coord is None: 1118 for row in self.traverse(): 1119 data.append(list(row.traverse())) 1120 transposed_data = zip_longest(*data) 1121 self.clear() 1122 # new_rows = [] 1123 for row_cells in transposed_data: 1124 if not isiterable(row_cells): 1125 row_cells = (row_cells,) 1126 row = Row() 1127 row.extend_cells(row_cells) 1128 self.append_row(row, clone=False) 1129 self._compute_table_cache() 1130 else: 1131 x, y, z, t = self._translate_table_coordinates(coord) 1132 if x is None: 1133 x = 0 1134 else: 1135 x = min(x, self.width - 1) 1136 if z is None: 1137 z = self.width - 1 1138 else: 1139 z = min(z, self.width - 1) 1140 if y is None: 1141 y = 0 1142 else: 1143 y = min(y, self.height - 1) 1144 if t is None: 1145 t = self.height - 1 1146 else: 1147 t = min(t, self.height - 1) 1148 for row in self.traverse(start=y, end=t): 1149 data.append(list(row.traverse(start=x, end=z))) 1150 transposed_data = zip_longest(*data) 1151 # clear locally 1152 w = z - x + 1 1153 h = t - y + 1 1154 if w != h: 1155 nones = [[None] * w for i in range(h)] 1156 self.set_values(nones, coord=(x, y, z, t)) 1157 # put transposed 1158 filtered_data: list[tuple[Cell]] = [] 1159 for row_cells in transposed_data: 1160 if isinstance(row_cells, (list, tuple)): 1161 filtered_data.append(row_cells) 1162 else: 1163 filtered_data.append((row_cells,)) 1164 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1165 self._compute_table_cache()
Swap in-place rows and columns of the table.
If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.
Arguments:
coord -- str or tuple of int : coordinates of area
start -- int or str
1167 def is_empty(self, aggressive: bool = False) -> bool: 1168 """Return whether every cell in the table has no value or the value 1169 evaluates to False (empty string), and no style. 1170 1171 If aggressive is True, empty cells with style are considered empty. 1172 1173 Arguments: 1174 1175 aggressive -- bool 1176 """ 1177 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
1186 def traverse( # noqa: C901 1187 self, 1188 start: int | None = None, 1189 end: int | None = None, 1190 ) -> Iterator[Row]: 1191 """Yield as many row elements as expected rows in the table, i.e. 1192 expand repetitions by returning the same row as many times as 1193 necessary. 1194 1195 Arguments: 1196 1197 start -- int 1198 1199 end -- int 1200 1201 Copies are returned, use set_row() to push them back. 1202 """ 1203 idx = -1 1204 before = -1 1205 y = 0 1206 if start is None and end is None: 1207 for juska in self._tmap: 1208 idx += 1 1209 if idx in self._indexes["_tmap"]: 1210 row = self._indexes["_tmap"][idx] 1211 else: 1212 row = self._get_element_idx2(_xpath_row_idx, idx) 1213 self._indexes["_tmap"][idx] = row 1214 repeated = juska - before 1215 before = juska 1216 for _i in range(repeated or 1): 1217 # Return a copy without the now obsolete repetition 1218 row = row.clone 1219 row.y = y 1220 y += 1 1221 if repeated > 1: 1222 row.repeated = None 1223 yield row 1224 else: 1225 if start is None: 1226 start = 0 1227 start = max(0, start) 1228 if end is None: 1229 try: 1230 end = self._tmap[-1] 1231 except Exception: 1232 end = -1 1233 start_map = find_odf_idx(self._tmap, start) 1234 if start_map is None: 1235 return 1236 if start_map > 0: 1237 before = self._tmap[start_map - 1] 1238 idx = start_map - 1 1239 before = start - 1 1240 y = start 1241 for juska in self._tmap[start_map:]: 1242 idx += 1 1243 if idx in self._indexes["_tmap"]: 1244 row = self._indexes["_tmap"][idx] 1245 else: 1246 row = self._get_element_idx2(_xpath_row_idx, idx) 1247 self._indexes["_tmap"][idx] = row 1248 repeated = juska - before 1249 before = juska 1250 for _i in range(repeated or 1): 1251 if y <= end: 1252 row = row.clone 1253 row.y = y 1254 y += 1 1255 if repeated > 1 or (y == start and start > 0): 1256 row.repeated = None 1257 yield row
Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_row() to push them back.
1259 def get_rows( 1260 self, 1261 coord: tuple | list | str | None = None, 1262 style: str | None = None, 1263 content: str | None = None, 1264 ) -> list[Row]: 1265 """Get the list of rows matching the criteria. 1266 1267 Filter by coordinates will parse the area defined by the coordinates. 1268 1269 Arguments: 1270 1271 coord -- str or tuple of int : coordinates of rows 1272 1273 content -- str regex 1274 1275 style -- str 1276 1277 Return: list of rows 1278 """ 1279 if coord: 1280 _x, y, _z, t = self._translate_table_coordinates(coord) 1281 else: 1282 y = t = None 1283 # fixme : not clones ? 1284 if not content and not style: 1285 return list(self.traverse(start=y, end=t)) 1286 rows = [] 1287 for row in self.traverse(start=y, end=t): 1288 if content and not row.match(content): 1289 continue 1290 if style and style != row.style: 1291 continue 1292 rows.append(row) 1293 return rows
Get the list of rows matching the criteria.
Filter by coordinates will parse the area defined by the coordinates.
Arguments:
coord -- str or tuple of int : coordinates of rows
content -- str regex
style -- str
Return: list of rows
1318 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1319 """Get the row at the given "y" position. 1320 1321 Position start at 0. So cell A4 is on row 3. 1322 1323 A copy is returned, use set_cell() to push it back. 1324 1325 Arguments: 1326 1327 y -- int or str 1328 1329 Return: Row 1330 """ 1331 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1332 y = self._translate_y_from_any(y) 1333 row = self._get_row2(y, clone=clone, create=create) 1334 if row is None: 1335 raise ValueError("Row not found") 1336 row.y = y 1337 return row
Get the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
A copy is returned, use set_cell() to push it back.
Arguments:
y -- int or str
Return: Row
1339 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1340 """Replace the row at the given position with the new one. Repetions of 1341 the old row will be adjusted. 1342 1343 If row is None, a new empty row is created. 1344 1345 Position start at 0. So cell A4 is on row 3. 1346 1347 Arguments: 1348 1349 y -- int or str 1350 1351 row -- Row 1352 1353 returns the row, with updated row.y 1354 """ 1355 if row is None: 1356 row = Row() 1357 repeated = 1 1358 clone = False 1359 else: 1360 repeated = row.repeated or 1 1361 y = self._translate_y_from_any(y) 1362 row.y = y 1363 # Outside the defined table ? 1364 diff = y - self.height 1365 if diff == 0: 1366 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1367 elif diff > 0: 1368 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1369 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1370 else: 1371 # Inside the defined table 1372 row_back = set_item_in_vault( # type: ignore 1373 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1374 ) 1375 # print self.serialize(True) 1376 # Update width if necessary 1377 self._update_width(row_back) 1378 return row_back
Replace the row at the given position with the new one. Repetions of the old row will be adjusted.
If row is None, a new empty row is created.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1380 def insert_row( 1381 self, y: str | int, row: Row | None = None, clone: bool = True 1382 ) -> Row: 1383 """Insert the row before the given "y" position. If no row is given, 1384 an empty one is created. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 If row is None, a new empty row is created. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 row -- Row 1395 1396 returns the row, with updated row.y 1397 """ 1398 if row is None: 1399 row = Row() 1400 clone = False 1401 y = self._translate_y_from_any(y) 1402 diff = y - self.height 1403 if diff < 0: 1404 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1405 elif diff == 0: 1406 row_back = self.append_row(row, clone=clone) 1407 else: 1408 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1409 row_back = self.append_row(row, clone=clone) 1410 row_back.y = y # type: ignore 1411 # Update width if necessary 1412 self._update_width(row_back) # type: ignore 1413 return row_back # type: ignore
Insert the row before the given "y" position. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
If row is None, a new empty row is created.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1415 def extend_rows(self, rows: list[Row] | None = None) -> None: 1416 """Append a list of rows at the end of the table. 1417 1418 Arguments: 1419 1420 rows -- list of Row 1421 """ 1422 if rows is None: 1423 rows = [] 1424 self.extend(rows) 1425 self._compute_table_cache() 1426 # Update width if necessary 1427 width = self.width 1428 for row in self.traverse(): 1429 if row.width > width: 1430 width = row.width 1431 diff = width - self.width 1432 if diff > 0: 1433 self.append_column(Column(repeated=diff))
Append a list of rows at the end of the table.
Arguments:
rows -- list of Row
1435 def append_row( 1436 self, 1437 row: Row | None = None, 1438 clone: bool = True, 1439 _repeated: int | None = None, 1440 ) -> Row: 1441 """Append the row at the end of the table. If no row is given, an 1442 empty one is created. 1443 1444 Position start at 0. So cell A4 is on row 3. 1445 1446 Note the columns are automatically created when the first row is 1447 inserted in an empty table. So better insert a filled row. 1448 1449 Arguments: 1450 1451 row -- Row 1452 1453 _repeated -- (optional), repeated value of the row 1454 1455 returns the row, with updated row.y 1456 """ 1457 if row is None: 1458 row = Row() 1459 _repeated = 1 1460 elif clone: 1461 row = row.clone 1462 # Appending a repeated row accepted 1463 # Do not insert next to the last row because it could be in a group 1464 self._append(row) 1465 if _repeated is None: 1466 _repeated = row.repeated or 1 1467 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1468 row.y = self.height - 1 1469 # Initialize columns 1470 if not self._get_columns(): 1471 repeated = row.width 1472 self.insert(Column(repeated=repeated), position=0) 1473 self._compute_table_cache() 1474 # Update width if necessary 1475 self._update_width(row) 1476 return row
Append the row at the end of the table. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.
Arguments:
row -- Row
_repeated -- (optional), repeated value of the row
returns the row, with updated row.y
1478 def delete_row(self, y: int | str) -> None: 1479 """Delete the row at the given "y" position. 1480 1481 Position start at 0. So cell A4 is on row 3. 1482 1483 Arguments: 1484 1485 y -- int or str 1486 """ 1487 y = self._translate_y_from_any(y) 1488 # Outside the defined table 1489 if y >= self.height: 1490 return 1491 # Inside the defined table 1492 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
Delete the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
1494 def get_row_values( 1495 self, 1496 y: int | str, 1497 cell_type: str | None = None, 1498 complete: bool = True, 1499 get_type: bool = False, 1500 ) -> list: 1501 """Shortcut to get the list of Python values for the cells of the row 1502 at the given "y" position. 1503 1504 Position start at 0. So cell A4 is on row 3. 1505 1506 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1507 type, aka non empty cells. 1508 If cell_type and complete is True, replace missing values by None. 1509 1510 If get_type is True, returns a tuple (value, ODF type of value) 1511 1512 Arguments: 1513 1514 y -- int, str 1515 1516 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1517 'currency', 'percentage' or 'all' 1518 1519 complete -- boolean 1520 1521 get_type -- boolean 1522 1523 Return: list of lists of Python types 1524 """ 1525 values = self.get_row(y, clone=False).get_values( 1526 cell_type=cell_type, complete=complete, get_type=get_type 1527 ) 1528 # complete row to match column width 1529 if complete: 1530 if get_type: 1531 values.extend([(None, None)] * (self.width - len(values))) 1532 else: 1533 values.extend([None] * (self.width - len(values))) 1534 return values
Shortcut to get the list of Python values for the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
y -- int, str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
1536 def set_row_values( 1537 self, 1538 y: int | str, 1539 values: list, 1540 cell_type: str | None = None, 1541 currency: str | None = None, 1542 style: str | None = None, 1543 ) -> Row: 1544 """Shortcut to set the values of *all* cells of the row at the given 1545 "y" position. 1546 1547 Position start at 0. So cell A4 is on row 3. 1548 1549 Arguments: 1550 1551 y -- int or str 1552 1553 values -- list of Python types 1554 1555 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1556 'string' or 'time' 1557 1558 currency -- three-letter str 1559 1560 style -- str 1561 1562 returns the row, with updated row.y 1563 """ 1564 row = Row() # needed if clones rows 1565 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1566 return self.set_row(y, row) # needed if clones rows
Shortcut to set the values of all cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
returns the row, with updated row.y
1568 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1569 """Shortcut to set *all* the cells of the row at the given 1570 "y" position. 1571 1572 Position start at 0. So cell A4 is on row 3. 1573 1574 Arguments: 1575 1576 y -- int or str 1577 1578 cells -- list of Python types 1579 1580 style -- str 1581 1582 returns the row, with updated row.y 1583 """ 1584 if cells is None: 1585 cells = [] 1586 row = Row() # needed if clones rows 1587 row.extend_cells(cells) 1588 return self.set_row(y, row) # needed if clones rows
Shortcut to set all the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
cells -- list of Python types
style -- str
returns the row, with updated row.y
1590 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1591 """Return wether every cell in the row at the given "y" position has 1592 no value or the value evaluates to False (empty string), and no style. 1593 1594 Position start at 0. So cell A4 is on row 3. 1595 1596 If aggressive is True, empty cells with style are considered empty. 1597 1598 Arguments: 1599 1600 y -- int or str 1601 1602 aggressive -- bool 1603 """ 1604 return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell A4 is on row 3.
If aggressive is True, empty cells with style are considered empty.
Arguments:
y -- int or str
aggressive -- bool
1610 def get_cells( 1611 self, 1612 coord: tuple | list | str | None = None, 1613 cell_type: str | None = None, 1614 style: str | None = None, 1615 content: str | None = None, 1616 flat: bool = False, 1617 ) -> list: 1618 """Get the cells matching the criteria. If 'coord' is None, 1619 parse the whole table, else parse the area defined by 'coord'. 1620 1621 Filter by cell_type = "all" will retrieve cells of any 1622 type, aka non empty cells. 1623 1624 If flat is True (default is False), the method return a single list 1625 of all the values, else a list of lists of cells. 1626 1627 if cell_type, style and content are None, get_cells() will return 1628 the exact number of cells of the area, including empty cells. 1629 1630 Arguments: 1631 1632 coordinates -- str or tuple of int : coordinates of area 1633 1634 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1635 'currency', 'percentage' or 'all' 1636 1637 content -- str regex 1638 1639 style -- str 1640 1641 flat -- boolean 1642 1643 Return: list of tuples 1644 """ 1645 if coord: 1646 x, y, z, t = self._translate_table_coordinates(coord) 1647 else: 1648 x = y = z = t = None 1649 if flat: 1650 cells: list[Cell] = [] 1651 for row in self.traverse(start=y, end=t): 1652 row_cells = row.get_cells( 1653 coord=(x, z), 1654 cell_type=cell_type, 1655 style=style, 1656 content=content, 1657 ) 1658 cells.extend(row_cells) 1659 return cells 1660 else: 1661 lcells: list[list[Cell]] = [] 1662 for row in self.traverse(start=y, end=t): 1663 row_cells = row.get_cells( 1664 coord=(x, z), 1665 cell_type=cell_type, 1666 style=style, 1667 content=content, 1668 ) 1669 lcells.append(row_cells) 1670 return lcells
Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.
Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.
If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.
if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.
Arguments:
coordinates -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
flat -- boolean
Return: list of tuples
1672 def get_cell( 1673 self, 1674 coord: tuple | list | str, 1675 clone: bool = True, 1676 keep_repeated: bool = True, 1677 ) -> Cell: 1678 """Get the cell at the given coordinates. 1679 1680 They are either a 2-uplet of (x, y) starting from 0, or a 1681 human-readable position like "C4". 1682 1683 A copy is returned, use ``set_cell`` to push it back. 1684 1685 Arguments: 1686 1687 coord -- (int, int) or str 1688 1689 Return: Cell 1690 """ 1691 x, y = self._translate_cell_coordinates(coord) 1692 if x is None: 1693 raise ValueError 1694 if y is None: 1695 raise ValueError 1696 # Outside the defined table 1697 if y >= self.height: 1698 cell = Cell() 1699 else: 1700 # Inside the defined table 1701 row = self._get_row2_base(y) 1702 if row is None: 1703 raise ValueError 1704 read_cell = row.get_cell(x, clone=clone) 1705 if read_cell is None: 1706 raise ValueError 1707 cell = read_cell 1708 if not keep_repeated: 1709 repeated = cell.repeated or 1 1710 if repeated >= 2: 1711 cell.repeated = None 1712 cell.x = x 1713 cell.y = y 1714 return cell
Get the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
A copy is returned, use set_cell to push it back.
Arguments:
coord -- (int, int) or str
Return: Cell
1716 def get_value( 1717 self, 1718 coord: tuple | list | str, 1719 get_type: bool = False, 1720 ) -> Any: 1721 """Shortcut to get the Python value of the cell at the given 1722 coordinates. 1723 1724 If get_type is True, returns the tuples (value, ODF type) 1725 1726 coord is either a 2-uplet of (x, y) starting from 0, or a 1727 human-readable position like "C4". If an Area is given, the upper 1728 left position is used as coord. 1729 1730 Arguments: 1731 1732 coord -- (int, int) or str : coordinate 1733 1734 Return: Python type 1735 """ 1736 x, y = self._translate_cell_coordinates(coord) 1737 if x is None: 1738 raise ValueError 1739 if y is None: 1740 raise ValueError 1741 # Outside the defined table 1742 if y >= self.height: 1743 if get_type: 1744 return (None, None) 1745 return None 1746 else: 1747 # Inside the defined table 1748 row = self._get_row2_base(y) 1749 if row is None: 1750 raise ValueError 1751 cell = row._get_cell2_base(x) 1752 if cell is None: 1753 if get_type: 1754 return (None, None) 1755 return None 1756 return cell.get_value(get_type=get_type)
Shortcut to get the Python value of the cell at the given coordinates.
If get_type is True, returns the tuples (value, ODF type)
coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.
Arguments:
coord -- (int, int) or str : coordinate
Return: Python type
1758 def set_cell( 1759 self, 1760 coord: tuple | list | str, 1761 cell: Cell | None = None, 1762 clone: bool = True, 1763 ) -> Cell: 1764 """Replace a cell of the table at the given coordinates. 1765 1766 They are either a 2-uplet of (x, y) starting from 0, or a 1767 human-readable position like "C4". 1768 1769 Arguments: 1770 1771 coord -- (int, int) or str : coordinate 1772 1773 cell -- Cell 1774 1775 return the cell, with x and y updated 1776 """ 1777 if cell is None: 1778 cell = Cell() 1779 clone = False 1780 x, y = self._translate_cell_coordinates(coord) 1781 if x is None: 1782 raise ValueError 1783 if y is None: 1784 raise ValueError 1785 cell.x = x 1786 cell.y = y 1787 if y >= self.height: 1788 row = Row() 1789 cell_back = row.set_cell(x, cell, clone=clone) 1790 self.set_row(y, row, clone=False) 1791 else: 1792 row_read = self._get_row2_base(y) 1793 if row_read is None: 1794 raise ValueError 1795 row = row_read 1796 row.y = y 1797 repeated = row.repeated or 1 1798 if repeated > 1: 1799 row = row.clone 1800 row.repeated = None 1801 cell_back = row.set_cell(x, cell, clone=clone) 1802 self.set_row(y, row, clone=False) 1803 else: 1804 cell_back = row.set_cell(x, cell, clone=clone) 1805 # Update width if necessary, since we don't use set_row 1806 self._update_width(row) 1807 return cell_back
Replace a cell of the table at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str : coordinate
cell -- Cell
return the cell, with x and y updated
1809 def set_cells( 1810 self, 1811 cells: list[list[Cell]] | list[tuple[Cell]], 1812 coord: tuple | list | str | None = None, 1813 clone: bool = True, 1814 ) -> None: 1815 """Set the cells in the table, from the 'coord' position. 1816 1817 'coord' is the coordinate of the upper left cell to be modified by 1818 values. If 'coord' is None, default to the position (0,0) ("A1"). 1819 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1820 area is used as coordinate. 1821 1822 The table is *not* cleared before the operation, to reset the table 1823 before setting cells, use table.clear(). 1824 1825 A list of lists is expected, with as many lists as rows to be set, and 1826 as many cell in each sublist as cells to be setted in the row. 1827 1828 Arguments: 1829 1830 cells -- list of list of cells 1831 1832 coord -- tuple or str 1833 1834 values -- list of lists of python types 1835 """ 1836 if coord: 1837 x, y = self._translate_cell_coordinates(coord) 1838 else: 1839 x = y = 0 1840 if y is None: 1841 y = 0 1842 if x is None: 1843 x = 0 1844 y -= 1 1845 for row_cells in cells: 1846 y += 1 1847 if not row_cells: 1848 continue 1849 row = self.get_row(y, clone=True) 1850 repeated = row.repeated or 1 1851 if repeated >= 2: 1852 row.repeated = None 1853 row.set_cells(row_cells, start=x, clone=clone) 1854 self.set_row(y, row, clone=False) 1855 self._update_width(row)
Set the cells in the table, from the 'coord' position.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting cells, use table.clear().
A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.
Arguments:
cells -- list of list of cells
coord -- tuple or str
values -- list of lists of python types
1857 def set_value( 1858 self, 1859 coord: tuple | list | str, 1860 value: Any, 1861 cell_type: str | None = None, 1862 currency: str | None = None, 1863 style: str | None = None, 1864 ) -> None: 1865 """Set the Python value of the cell at the given coordinates. 1866 1867 They are either a 2-uplet of (x, y) starting from 0, or a 1868 human-readable position like "C4". 1869 1870 Arguments: 1871 1872 coord -- (int, int) or str 1873 1874 value -- Python type 1875 1876 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1877 'string' or 'time' 1878 1879 currency -- three-letter str 1880 1881 style -- str 1882 1883 """ 1884 self.set_cell( 1885 coord, 1886 Cell(value, cell_type=cell_type, currency=currency, style=style), 1887 clone=False, 1888 )
Set the Python value of the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1890 def set_cell_image( 1891 self, 1892 coord: tuple | list | str, 1893 image_frame: Frame, 1894 doc_type: str | None = None, 1895 ) -> None: 1896 """Do all the magic to display an image in the cell at the given 1897 coordinates. 1898 1899 They are either a 2-uplet of (x, y) starting from 0, or a 1900 human-readable position like "C4". 1901 1902 The frame element must contain the expected image position and 1903 dimensions. 1904 1905 DrawImage insertion depends on the document type, so the type must be 1906 provided or the table element must be already attached to a document. 1907 1908 Arguments: 1909 1910 coord -- (int, int) or str 1911 1912 image_frame -- Frame including an image 1913 1914 doc_type -- 'spreadsheet' or 'text' 1915 """ 1916 # Test document type 1917 if doc_type is None: 1918 body = self.document_body 1919 if body is None: 1920 raise ValueError("document type not found") 1921 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1922 body.tag 1923 ) 1924 if doc_type is None: 1925 raise ValueError("document type not supported for images") 1926 # We need the end address of the image 1927 x, y = self._translate_cell_coordinates(coord) 1928 if x is None: 1929 raise ValueError 1930 if y is None: 1931 raise ValueError 1932 cell = self.get_cell((x, y)) 1933 image_frame = image_frame.clone # type: ignore 1934 # Remove any previous paragraph, frame, etc. 1935 for child in cell.children: 1936 cell.delete(child) 1937 # Now it all depends on the document type 1938 if doc_type == "spreadsheet": 1939 image_frame.anchor_type = "char" 1940 # The frame needs end coordinates 1941 width, height = image_frame.size 1942 image_frame.set_attribute("table:end-x", width) 1943 image_frame.set_attribute("table:end-y", height) 1944 # FIXME what happens when the address changes? 1945 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 1946 image_frame.set_attribute("table:end-cell-address", address) 1947 # The frame is directly in the cell 1948 cell.append(image_frame) 1949 elif doc_type == "text": 1950 # The frame must be in a paragraph 1951 cell.set_value("") 1952 paragraph = cell.get_element("text:p") 1953 if paragraph is None: 1954 raise ValueError 1955 paragraph.append(image_frame) 1956 self.set_cell(coord, cell)
Do all the magic to display an image in the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
The frame element must contain the expected image position and dimensions.
DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.
Arguments:
coord -- (int, int) or str
image_frame -- Frame including an image
doc_type -- 'spreadsheet' or 'text'
1958 def insert_cell( 1959 self, 1960 coord: tuple | list | str, 1961 cell: Cell | None = None, 1962 clone: bool = True, 1963 ) -> Cell: 1964 """Insert the given cell at the given coordinates. If no cell is 1965 given, an empty one is created. 1966 1967 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 1968 human-readable position like "C4". 1969 1970 Cells on the right are shifted. Other rows remain untouched. 1971 1972 Arguments: 1973 1974 coord -- (int, int) or str 1975 1976 cell -- Cell 1977 1978 returns the cell with x and y updated 1979 """ 1980 if cell is None: 1981 cell = Cell() 1982 clone = False 1983 if clone: 1984 cell = cell.clone 1985 x, y = self._translate_cell_coordinates(coord) 1986 if x is None: 1987 raise ValueError 1988 if y is None: 1989 raise ValueError 1990 row = self._get_row2(y, clone=True) 1991 row.y = y 1992 row.repeated = None 1993 cell_back = row.insert_cell(x, cell, clone=False) 1994 self.set_row(y, row, clone=False) 1995 # Update width if necessary 1996 self._update_width(row) 1997 return cell_back
Insert the given cell at the given coordinates. If no cell is given, an empty one is created.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Cells on the right are shifted. Other rows remain untouched.
Arguments:
coord -- (int, int) or str
cell -- Cell
returns the cell with x and y updated
1999 def append_cell( 2000 self, 2001 y: int | str, 2002 cell: Cell | None = None, 2003 clone: bool = True, 2004 ) -> Cell: 2005 """Append the given cell at the "y" coordinate. Repeated cells are 2006 accepted. If no cell is given, an empty one is created. 2007 2008 Position start at 0. So cell A4 is on row 3. 2009 2010 Other rows remain untouched. 2011 2012 Arguments: 2013 2014 y -- int or str 2015 2016 cell -- Cell 2017 2018 returns the cell with x and y updated 2019 """ 2020 if cell is None: 2021 cell = Cell() 2022 clone = False 2023 if clone: 2024 cell = cell.clone 2025 y = self._translate_y_from_any(y) 2026 row = self._get_row2(y) 2027 row.y = y 2028 cell_back = row.append_cell(cell, clone=False) 2029 self.set_row(y, row) 2030 # Update width if necessary 2031 self._update_width(row) 2032 return cell_back
Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Other rows remain untouched.
Arguments:
y -- int or str
cell -- Cell
returns the cell with x and y updated
2034 def delete_cell(self, coord: tuple | list | str) -> None: 2035 """Delete the cell at the given coordinates, so that next cells are 2036 shifted to the left. 2037 2038 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2039 human-readable position like "C4". 2040 2041 Use set_value() for erasing value. 2042 2043 Arguments: 2044 2045 coord -- (int, int) or str 2046 """ 2047 x, y = self._translate_cell_coordinates(coord) 2048 if x is None: 2049 raise ValueError 2050 if y is None: 2051 raise ValueError 2052 # Outside the defined table 2053 if y >= self.height: 2054 return 2055 # Inside the defined table 2056 row = self._get_row2_base(y) 2057 if row is None: 2058 raise ValueError 2059 row.delete_cell(x) 2060 # self.set_row(y, row)
Delete the cell at the given coordinates, so that next cells are shifted to the left.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Use set_value() for erasing value.
Arguments:
coord -- (int, int) or str
2067 def traverse_columns( # noqa: C901 2068 self, 2069 start: int | None = None, 2070 end: int | None = None, 2071 ) -> Iterator[Column]: 2072 """Yield as many column elements as expected columns in the table, 2073 i.e. expand repetitions by returning the same column as many times as 2074 necessary. 2075 2076 Arguments: 2077 2078 start -- int 2079 2080 end -- int 2081 2082 Copies are returned, use set_column() to push them back. 2083 """ 2084 idx = -1 2085 before = -1 2086 x = 0 2087 if start is None and end is None: 2088 for juska in self._cmap: 2089 idx += 1 2090 if idx in self._indexes["_cmap"]: 2091 column = self._indexes["_cmap"][idx] 2092 else: 2093 column = self._get_element_idx2(_xpath_column_idx, idx) 2094 self._indexes["_cmap"][idx] = column 2095 repeated = juska - before 2096 before = juska 2097 for _i in range(repeated or 1): 2098 # Return a copy without the now obsolete repetition 2099 column = column.clone 2100 column.x = x 2101 x += 1 2102 if repeated > 1: 2103 column.repeated = None 2104 yield column 2105 else: 2106 if start is None: 2107 start = 0 2108 start = max(0, start) 2109 if end is None: 2110 try: 2111 end = self._cmap[-1] 2112 except Exception: 2113 end = -1 2114 start_map = find_odf_idx(self._cmap, start) 2115 if start_map is None: 2116 return 2117 if start_map > 0: 2118 before = self._cmap[start_map - 1] 2119 idx = start_map - 1 2120 before = start - 1 2121 x = start 2122 for juska in self._cmap[start_map:]: 2123 idx += 1 2124 if idx in self._indexes["_cmap"]: 2125 column = self._indexes["_cmap"][idx] 2126 else: 2127 column = self._get_element_idx2(_xpath_column_idx, idx) 2128 self._indexes["_cmap"][idx] = column 2129 repeated = juska - before 2130 before = juska 2131 for _i in range(repeated or 1): 2132 if x <= end: 2133 column = column.clone 2134 column.x = x 2135 x += 1 2136 if repeated > 1 or (x == start and start > 0): 2137 column.repeated = None 2138 yield column
Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_column() to push them back.
2140 def get_columns( 2141 self, 2142 coord: tuple | list | str | None = None, 2143 style: str | None = None, 2144 ) -> list[Column]: 2145 """Get the list of columns matching the criteria. Each result is a 2146 tuple of (x, column). 2147 2148 Arguments: 2149 2150 coord -- str or tuple of int : coordinates of columns 2151 2152 style -- str 2153 2154 Return: list of columns 2155 """ 2156 if coord: 2157 x, _y, _z, t = self._translate_column_coordinates(coord) 2158 else: 2159 x = t = None 2160 if not style: 2161 return list(self.traverse_columns(start=x, end=t)) 2162 columns = [] 2163 for column in self.traverse_columns(start=x, end=t): 2164 if style != column.style: 2165 continue 2166 columns.append(column) 2167 return columns
Get the list of columns matching the criteria. Each result is a tuple of (x, column).
Arguments:
coord -- str or tuple of int : coordinates of columns
style -- str
Return: list of columns
2184 def get_column(self, x: int | str) -> Column: 2185 """Get the column at the given "x" position. 2186 2187 ODF columns don't contain cells, only style information. 2188 2189 Position start at 0. So cell C4 is on column 2. Alphabetical position 2190 like "C" is accepted. 2191 2192 A copy is returned, use set_column() to push it back. 2193 2194 Arguments: 2195 2196 x -- int or str 2197 2198 Return: Column 2199 """ 2200 x = self._translate_x_from_any(x) 2201 column = self._get_column2(x) 2202 if column is None: 2203 raise ValueError 2204 column.x = x 2205 return column
Get the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
A copy is returned, use set_column() to push it back.
Arguments:
x -- int or str
Return: Column
2207 def set_column( 2208 self, 2209 x: int | str, 2210 column: Column | None = None, 2211 ) -> Column: 2212 """Replace the column at the given "x" position. 2213 2214 ODF columns don't contain cells, only style information. 2215 2216 Position start at 0. So cell C4 is on column 2. Alphabetical position 2217 like "C" is accepted. 2218 2219 Arguments: 2220 2221 x -- int or str 2222 2223 column -- Column 2224 """ 2225 x = self._translate_x_from_any(x) 2226 if column is None: 2227 column = Column() 2228 repeated = 1 2229 else: 2230 repeated = column.repeated or 1 2231 column.x = x 2232 # Outside the defined table ? 2233 diff = x - self.width 2234 if diff == 0: 2235 column_back = self.append_column(column, _repeated=repeated) 2236 elif diff > 0: 2237 self.append_column(Column(repeated=diff), _repeated=diff) 2238 column_back = self.append_column(column, _repeated=repeated) 2239 else: 2240 # Inside the defined table 2241 column_back = set_item_in_vault( # type: ignore 2242 x, column, self, _xpath_column_idx, "_cmap" 2243 ) 2244 return column_back
Replace the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2246 def insert_column( 2247 self, 2248 x: int | str, 2249 column: Column | None = None, 2250 ) -> Column: 2251 """Insert the column before the given "x" position. If no column is 2252 given, an empty one is created. 2253 2254 ODF columns don't contain cells, only style information. 2255 2256 Position start at 0. So cell C4 is on column 2. Alphabetical position 2257 like "C" is accepted. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 column -- Column 2264 """ 2265 if column is None: 2266 column = Column() 2267 x = self._translate_x_from_any(x) 2268 diff = x - self.width 2269 if diff < 0: 2270 column_back = insert_item_in_vault( 2271 x, column, self, _xpath_column_idx, "_cmap" 2272 ) 2273 elif diff == 0: 2274 column_back = self.append_column(column.clone) 2275 else: 2276 self.append_column(Column(repeated=diff), _repeated=diff) 2277 column_back = self.append_column(column.clone) 2278 column_back.x = x # type: ignore 2279 # Repetitions are accepted 2280 repeated = column.repeated or 1 2281 # Update width on every row 2282 for row in self._get_rows(): 2283 if row.width > x: 2284 row.insert_cell(x, Cell(repeated=repeated)) 2285 # Shorter rows don't need insert 2286 # Longer rows shouldn't exist! 2287 return column_back # type: ignore
Insert the column before the given "x" position. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2289 def append_column( 2290 self, 2291 column: Column | None = None, 2292 _repeated: int | None = None, 2293 ) -> Column: 2294 """Append the column at the end of the table. If no column is given, 2295 an empty one is created. 2296 2297 ODF columns don't contain cells, only style information. 2298 2299 Position start at 0. So cell C4 is on column 2. Alphabetical position 2300 like "C" is accepted. 2301 2302 Arguments: 2303 2304 column -- Column 2305 """ 2306 if column is None: 2307 column = Column() 2308 else: 2309 column = column.clone 2310 if not self._cmap: 2311 position = 0 2312 else: 2313 odf_idx = len(self._cmap) - 1 2314 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2315 if last_column is None: 2316 raise ValueError 2317 position = self.index(last_column) + 1 2318 column.x = self.width 2319 self.insert(column, position=position) 2320 # Repetitions are accepted 2321 if _repeated is None: 2322 _repeated = column.repeated or 1 2323 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2324 # No need to update row widths 2325 return column
Append the column at the end of the table. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
column -- Column
2327 def delete_column(self, x: int | str) -> None: 2328 """Delete the column at the given position. ODF columns don't contain 2329 cells, only style information. 2330 2331 Position start at 0. So cell C4 is on column 2. Alphabetical position 2332 like "C" is accepted. 2333 2334 Arguments: 2335 2336 x -- int or str 2337 """ 2338 x = self._translate_x_from_any(x) 2339 # Outside the defined table 2340 if x >= self.width: 2341 return 2342 # Inside the defined table 2343 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2344 # Update width 2345 width = self.width 2346 for row in self._get_rows(): 2347 if row.width >= width: 2348 row.delete_cell(x)
Delete the column at the given position. ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
2350 def get_column_cells( # noqa: C901 2351 self, 2352 x: int | str, 2353 style: str | None = None, 2354 content: str | None = None, 2355 cell_type: str | None = None, 2356 complete: bool = False, 2357 ) -> list[Cell | None]: 2358 """Get the list of cells at the given position. 2359 2360 Position start at 0. So cell C4 is on column 2. Alphabetical position 2361 like "C" is accepted. 2362 2363 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2364 type, aka non empty cells. 2365 2366 If complete is True, replace missing values by None. 2367 2368 Arguments: 2369 2370 x -- int or str 2371 2372 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2373 'currency', 'percentage' or 'all' 2374 2375 content -- str regex 2376 2377 style -- str 2378 2379 complete -- boolean 2380 2381 Return: list of Cell 2382 """ 2383 x = self._translate_x_from_any(x) 2384 if cell_type: 2385 cell_type = cell_type.lower().strip() 2386 cells: list[Cell | None] = [] 2387 if not style and not content and not cell_type: 2388 for row in self.traverse(): 2389 cells.append(row.get_cell(x, clone=True)) 2390 return cells 2391 for row in self.traverse(): 2392 cell = row.get_cell(x, clone=True) 2393 if cell is None: 2394 raise ValueError 2395 # Filter the cells by cell_type 2396 if cell_type: 2397 ctype = cell.type 2398 if not ctype or not (ctype == cell_type or cell_type == "all"): 2399 if complete: 2400 cells.append(None) 2401 continue 2402 # Filter the cells with the regex 2403 if content and not cell.match(content): 2404 if complete: 2405 cells.append(None) 2406 continue 2407 # Filter the cells with the style 2408 if style and style != cell.style: 2409 if complete: 2410 cells.append(None) 2411 continue 2412 cells.append(cell) 2413 return cells
Get the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
If complete is True, replace missing values by None.
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
complete -- boolean
Return: list of Cell
2415 def get_column_values( 2416 self, 2417 x: int | str, 2418 cell_type: str | None = None, 2419 complete: bool = True, 2420 get_type: bool = False, 2421 ) -> list[Any]: 2422 """Shortcut to get the list of Python values for the cells at the 2423 given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 If cell_type and complete is True, replace missing values by None. 2431 2432 If get_type is True, returns a tuple (value, ODF type of value) 2433 2434 Arguments: 2435 2436 x -- int or str 2437 2438 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2439 'currency', 'percentage' or 'all' 2440 2441 complete -- boolean 2442 2443 get_type -- boolean 2444 2445 Return: list of Python types 2446 """ 2447 cells = self.get_column_cells( 2448 x, style=None, content=None, cell_type=cell_type, complete=complete 2449 ) 2450 values: list[Any] = [] 2451 for cell in cells: 2452 if cell is None: 2453 if complete: 2454 if get_type: 2455 values.append((None, None)) 2456 else: 2457 values.append(None) 2458 continue 2459 if cell_type: 2460 ctype = cell.type 2461 if not ctype or not (ctype == cell_type or cell_type == "all"): 2462 if complete: 2463 if get_type: 2464 values.append((None, None)) 2465 else: 2466 values.append(None) 2467 continue 2468 values.append(cell.get_value(get_type=get_type)) 2469 return values
Shortcut to get the list of Python values for the cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types
2471 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2472 """Shortcut to set the list of cells at the given position. 2473 2474 Position start at 0. So cell C4 is on column 2. Alphabetical position 2475 like "C" is accepted. 2476 2477 The list must have the same length than the table height. 2478 2479 Arguments: 2480 2481 x -- int or str 2482 2483 cells -- list of Cell 2484 """ 2485 height = self.height 2486 if len(cells) != height: 2487 raise ValueError(f"col mismatch: {height} cells expected") 2488 cells_iterator = iter(cells) 2489 for y, row in enumerate(self.traverse()): 2490 row.set_cell(x, next(cells_iterator)) 2491 self.set_row(y, row)
Shortcut to set the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
cells -- list of Cell
2493 def set_column_values( 2494 self, 2495 x: int | str, 2496 values: list, 2497 cell_type: str | None = None, 2498 currency: str | None = None, 2499 style: str | None = None, 2500 ) -> None: 2501 """Shortcut to set the list of Python values of cells at the given 2502 position. 2503 2504 Position start at 0. So cell C4 is on column 2. Alphabetical position 2505 like "C" is accepted. 2506 2507 The list must have the same length than the table height. 2508 2509 Arguments: 2510 2511 x -- int or str 2512 2513 values -- list of Python types 2514 2515 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2516 'string' or 'time' 2517 2518 currency -- three-letter str 2519 2520 style -- str 2521 """ 2522 cells = [ 2523 Cell(value, cell_type=cell_type, currency=currency, style=style) 2524 for value in values 2525 ] 2526 self.set_column_cells(x, cells)
Shortcut to set the list of Python values of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
2528 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2529 """Return wether every cell in the column at "x" position has no value 2530 or the value evaluates to False (empty string), and no style. 2531 2532 Position start at 0. So cell C4 is on column 2. Alphabetical position 2533 like "C" is accepted. 2534 2535 If aggressive is True, empty cells with style are considered empty. 2536 2537 Return: bool 2538 """ 2539 for cell in self.get_column_cells(x): 2540 if cell is None: 2541 continue 2542 if not cell.is_empty(aggressive=aggressive): 2543 return False 2544 return True
Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
If aggressive is True, empty cells with style are considered empty.
Return: bool
2548 def get_named_ranges( # type: ignore 2549 self, 2550 table_name: str | list[str] | None = None, 2551 ) -> list[NamedRange]: 2552 """Returns the list of available Name Ranges of the spreadsheet. If 2553 table_name is provided, limits the search to these tables. 2554 Beware : named ranges are stored at the body level, thus do not call 2555 this method on a cloned table. 2556 2557 Arguments: 2558 2559 table_names -- str or list of str, names of tables 2560 2561 Return : list of table_range 2562 """ 2563 body = self.document_body 2564 if not body: 2565 return [] 2566 all_named_ranges = body.get_named_ranges() 2567 if not table_name: 2568 return all_named_ranges # type:ignore 2569 filter_ = [] 2570 if isinstance(table_name, str): 2571 filter_.append(table_name) 2572 elif isiterable(table_name): 2573 filter_.extend(table_name) 2574 else: 2575 raise ValueError( 2576 f"table_name must be string or Iterable, not {type(table_name)}" 2577 ) 2578 return [ 2579 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2580 ]
Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
table_names -- str or list of str, names of tables
Return : list of table_range
2582 def get_named_range(self, name: str) -> NamedRange: 2583 """Returns the Name Ranges of the specified name. If 2584 table_name is provided, limits the search to these tables. 2585 Beware : named ranges are stored at the body level, thus do not call 2586 this method on a cloned table. 2587 2588 Arguments: 2589 2590 name -- str, name of the named range object 2591 2592 Return : NamedRange 2593 """ 2594 body = self.document_body 2595 if not body: 2596 raise ValueError("Table is not inside a document") 2597 return body.get_named_range(name) # type: ignore
Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range object
Return : NamedRange
2599 def set_named_range( 2600 self, 2601 name: str, 2602 crange: str | tuple | list, 2603 table_name: str | None = None, 2604 usage: str | None = None, 2605 ) -> None: 2606 """Create a Named Range element and insert it in the document. 2607 Beware : named ranges are stored at the body level, thus do not call 2608 this method on a cloned table. 2609 2610 Arguments: 2611 2612 name -- str, name of the named range 2613 2614 crange -- str or tuple of int, cell or area coordinate 2615 2616 table_name -- str, name of the table 2617 2618 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2619 """ 2620 body = self.document_body 2621 if not body: 2622 raise ValueError("Table is not inside a document") 2623 if not name: 2624 raise ValueError("Name required.") 2625 if table_name is None: 2626 table_name = self.name 2627 named_range = NamedRange(name, crange, table_name, usage) 2628 body.append_named_range(named_range)
Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2630 def delete_named_range(self, name: str) -> None: 2631 """Delete the Named Range of specified name from the spreadsheet. 2632 Beware : named ranges are stored at the body level, thus do not call 2633 this method on a cloned table. 2634 2635 Arguments: 2636 2637 name -- str 2638 """ 2639 name = name.strip() 2640 if not name: 2641 raise ValueError("Name required.") 2642 body = self.document_body 2643 if not body: 2644 raise ValueError("Table is not inside a document.") 2645 body.delete_named_range(name)
Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str
2651 def set_span( # noqa: C901 2652 self, 2653 area: str | tuple | list, 2654 merge: bool = False, 2655 ) -> bool: 2656 """Create a Cell Span : span the first cell of the area on several 2657 columns and/or rows. 2658 If merge is True, replace text of the cell by the concatenation of 2659 existing text in covered cells. 2660 Beware : if merge is True, old text is changed, if merge is False 2661 (the default), old text in coverd cells is still present but not 2662 displayed by most GUI. 2663 2664 If the area defines only one cell, the set span will do nothing. 2665 It is not allowed to apply set span to an area whose one cell already 2666 belongs to previous cell span. 2667 2668 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2669 be provided as an alpha numeric value like "A1:B2' or a tuple like 2670 (0, 0, 1, 1) or (0, 0). 2671 2672 Arguments: 2673 2674 area -- str or tuple of int, cell or area coordinate 2675 2676 merge -- boolean 2677 """ 2678 # get area 2679 digits = convert_coordinates(area) 2680 if len(digits) == 4: 2681 x, y, z, t = digits 2682 else: 2683 x, y = digits 2684 z, t = digits 2685 start = x, y 2686 end = z, t 2687 if start == end: 2688 # one cell : do nothing 2689 return False 2690 if x is None: 2691 raise ValueError 2692 if y is None: 2693 raise ValueError 2694 if z is None: 2695 raise ValueError 2696 if t is None: 2697 raise ValueError 2698 # check for previous span 2699 good = True 2700 # Check boundaries and empty cells : need to crate non existent cells 2701 # so don't use get_cells directly, but get_cell 2702 cells = [] 2703 for yy in range(y, t + 1): 2704 row_cells = [] 2705 for xx in range(x, z + 1): 2706 row_cells.append( 2707 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2708 ) 2709 cells.append(row_cells) 2710 for row in cells: 2711 for cell in row: 2712 if cell._is_spanned(): 2713 good = False 2714 break 2715 if not good: 2716 break 2717 if not good: 2718 return False 2719 # Check boundaries 2720 # if z >= self.width or t >= self.height: 2721 # self.set_cell(coord = end) 2722 # print area, z, t 2723 # cells = self.get_cells((x, y, z, t)) 2724 # print cells 2725 # do it: 2726 if merge: 2727 val_list = [] 2728 for row in cells: 2729 for cell in row: 2730 if cell.is_empty(aggressive=True): 2731 continue 2732 val = cell.get_value() 2733 if val is not None: 2734 if isinstance(val, str): 2735 val.strip() 2736 if val != "": 2737 val_list.append(val) 2738 cell.clear() 2739 if val_list: 2740 if len(val_list) == 1: 2741 cells[0][0].set_value(val_list[0]) 2742 else: 2743 value = " ".join([str(v) for v in val_list if v]) 2744 cells[0][0].set_value(value) 2745 cols = z - x + 1 2746 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2747 rows = t - y + 1 2748 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2749 for cell in cells[0][1:]: 2750 cell.tag = "table:covered-table-cell" 2751 for row in cells[1:]: 2752 for cell in row: 2753 cell.tag = "table:covered-table-cell" 2754 # replace cells in table 2755 self.set_cells(cells, coord=start, clone=False) 2756 return True
Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.
If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
area -- str or tuple of int, cell or area coordinate
merge -- boolean
2758 def del_span(self, area: str | tuple | list) -> bool: 2759 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2760 cell of the spanned area. 2761 2762 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2763 be provided as an alpha numeric value like "A1:B2' or a tuple like 2764 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2765 is used. 2766 2767 Arguments: 2768 2769 area -- str or tuple of int, cell or area coordinate 2770 """ 2771 # get area 2772 digits = convert_coordinates(area) 2773 if len(digits) == 4: 2774 x, y, _z, _t = digits 2775 else: 2776 x, y = digits 2777 if x is None: 2778 raise ValueError 2779 if y is None: 2780 raise ValueError 2781 start = x, y 2782 # check for previous span 2783 cell0 = self.get_cell(start) 2784 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2785 if nb_cols is None: 2786 return False 2787 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2788 if nb_rows is None: 2789 return False 2790 z = x + nb_cols - 1 2791 t = y + nb_rows - 1 2792 cells = self.get_cells((x, y, z, t)) 2793 cells[0][0].del_attribute("table:number-columns-spanned") 2794 cells[0][0].del_attribute("table:number-rows-spanned") 2795 for cell in cells[0][1:]: 2796 cell.tag = "table:table-cell" 2797 for row in cells[1:]: 2798 for cell in row: 2799 cell.tag = "table:table-cell" 2800 # replace cells in table 2801 self.set_cells(cells, coord=start, clone=False) 2802 return True
Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.
Arguments:
area -- str or tuple of int, cell or area coordinate
2806 def to_csv( 2807 self, 2808 path_or_file: str | Path | None = None, 2809 dialect: str = "excel", 2810 ) -> Any: 2811 """Write the table as CSV in the file. 2812 2813 If the file is a string, it is opened as a local path. Else an 2814 opened file-like is expected. 2815 2816 Arguments: 2817 2818 path_or_file -- str or file-like 2819 2820 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2821 """ 2822 2823 def write_content(csv_writer: object) -> None: 2824 for values in self.iter_values(): 2825 line = [] 2826 for value in values: 2827 if value is None: 2828 value = "" 2829 if isinstance(value, str): 2830 value = value.strip() 2831 line.append(value) 2832 csv_writer.writerow(line) # type: ignore 2833 2834 out = StringIO(newline="") 2835 csv_writer = csv.writer(out, dialect=dialect) 2836 write_content(csv_writer) 2837 if path_or_file is None: 2838 return out.getvalue() 2839 path = Path(path_or_file) 2840 path.write_text(out.getvalue()) 2841 return None
Write the table as CSV in the file.
If the file is a string, it is opened as a local path. Else an opened file-like is expected.
Arguments:
path_or_file -- str or file-like
dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- append_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
275class Text(str): 276 """Representation of an XML text node. Created to hide the specifics of 277 lxml in searching text nodes using XPath. 278 279 Constructed like any str object but only accepts lxml text objects. 280 """ 281 282 # There's some black magic in inheriting from str 283 def __init__( 284 self, 285 text_result: _ElementUnicodeResult | _ElementStringResult, 286 ) -> None: 287 self.__parent = text_result.getparent() 288 self.__is_text = text_result.is_text 289 self.__is_tail = text_result.is_tail 290 291 @property 292 def parent(self) -> Element | None: 293 parent = self.__parent 294 # XXX happens just because of the unit test 295 if parent is None: 296 return None 297 return Element.from_tag(tag_or_elem=parent) 298 299 def is_text(self) -> bool: 300 return self.__is_text 301 302 def is_tail(self) -> bool: 303 return self.__is_tail
Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.
Constructed like any str object but only accepts lxml text objects.
Inherited Members
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
508class TextChange(Element): 509 """The TextChange "text:change" element marks a position in an empty 510 region where text has been deleted. 511 """ 512 513 _tag = "text:change" 514 515 def get_id(self) -> str | None: 516 return self.get_attribute_string("text:change-id") 517 518 def set_id(self, idx: str) -> None: 519 self.set_attribute("text:change-id", idx) 520 521 def _get_tracked_changes(self) -> Element | None: 522 body = self.document_body 523 if not body: 524 raise ValueError 525 return body.get_tracked_changes() 526 527 def get_changed_region( 528 self, 529 tracked_changes: Element | None = None, 530 ) -> Element | None: 531 if not tracked_changes: 532 tracked_changes = self._get_tracked_changes() 533 idx = self.get_id() 534 return tracked_changes.get_changed_region(text_id=idx) # type: ignore 535 536 def get_change_info( 537 self, 538 tracked_changes: Element | None = None, 539 ) -> Element | None: 540 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 541 if not changed_region: 542 return None 543 return changed_region.get_change_info() # type: ignore 544 545 def get_change_element( 546 self, 547 tracked_changes: Element | None = None, 548 ) -> Element | None: 549 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 550 if not changed_region: 551 return None 552 return changed_region.get_change_element() # type: ignore 553 554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 ) 574 575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None 583 584 def get_start(self) -> TextChangeStart | None: 585 """Return None.""" 586 return None 587 588 def get_end(self) -> TextChangeEnd | None: 589 """Return None.""" 590 return None
The TextChange "text:change" element marks a position in an empty region where text has been deleted.
554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 )
Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.
Return: Paragraph (or None)."
575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
593class TextChangeEnd(TextChange): 594 """The TextChangeEnd "text:change-end" element marks the end of a region 595 with content where text has been inserted or the format has been 596 changed. 597 """ 598 599 _tag = "text:change-end" 600 601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore 611 612 def get_end(self) -> TextChangeEnd | None: 613 """Return self.""" 614 return self 615 616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None 619 620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.
601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore
Return the corresponding annotation starting tag or None.
616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None
Return None.
620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
Return the content between text:change-start and text:change-end.
If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
661class TextChangeStart(TextChangeEnd): 662 """The TextChangeStart "text:change-start" element marks the start of a 663 region with content where text has been inserted or the format has 664 been changed. 665 """ 666 667 _tag = "text:change-start" 668 669 def get_start(self) -> TextChangeStart: 670 """Return self.""" 671 return self 672 673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore 683 684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.
673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore
Return the corresponding change-end tag or None.
684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For TextChangeStart : delete also the end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
358class TextChangedRegion(Element): 359 """Each TextChangedRegion "text:changed-region" element contains a single 360 element, one of TextInsertion, TextDeletion or TextFormatChange that 361 corresponds to a change being tracked within the scope of the 362 "text:tracked-changes" element that contains the "text:changed-region" 363 instance. 364 The xml:id attribute of the TextChangedRegion is referenced 365 from the "text:change", "text:change-start" and "text:change-end" 366 elements that identify where the change applies to markup in the scope of 367 the "text:tracked-changes" element. 368 369 Warning : for this implementation, text:change should be referenced only 370 once in the scope, which is different from ODF 1.2 requirement: 371 " A "text:changed-region" can be referenced by more than one 372 change, but the corresponding referencing change mark elements 373 shall be of the same change type - insertion, format change or 374 deletion. " 375 """ 376 377 _tag = "text:changed-region" 378 379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info") 386 387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 ) 413 414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0) 426 427 def _get_text_id(self) -> str | None: 428 return self.get_attribute_string("text:id") 429 430 def _set_text_id(self, text_id: str) -> None: 431 self.set_attribute("text:id", text_id) 432 433 def _get_xml_id(self) -> str | None: 434 return self.get_attribute_string("xml:id") 435 436 def _set_xml_id(self, xml_id: str) -> None: 437 self.set_attribute("xml:id", xml_id) 438 439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id() 445 446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.
Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "
379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info")
Shortcut to get the ChangeInfo element of the change element child.
Return: ChangeInfo element.
387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 )
Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0)
Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.
Return: Element.
439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id()
Get the "text:id" attribute.
Return: str
446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Set both the "text:id" and "xml:id" attributes with same value.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
251class TextDeletion(TextInsertion): 252 """The TextDeletion "text:deletion" contains information that identifies 253 the person responsible for a deletion and the date of that deletion. 254 This information may also contain one or more Paragraph which contains 255 a comment on the deletion. The TextDeletion element may also contain 256 content that was deleted while change tracking was enabled. The position 257 where the text was deleted is marked by a "text:change" element. Deleted 258 text is contained in a paragraph element. To reconstruct the original 259 text, the paragraph containing the deleted text is merged with its 260 surrounding paragraph or heading element. To reconstruct the text before 261 a deletion took place: 262 - If the change mark is inside a paragraph, insert the content that was 263 deleted, but remove all leading start tags up to and including the 264 first "text:p" element and all trailing end tags up to and including 265 the last "/text:p" or "/text:h" element. If the last trailing element 266 is a "/text:h", change the end tag "/text:p" following this insertion 267 to a "/text:h" element. 268 - If the change mark is inside a heading, insert the content that was 269 deleted, but remove all leading start tags up to and including the 270 first "text:h" element and all trailing end tags up to and including 271 the last "/text:h" or "/text:p" element. If the last trailing element 272 is a "/text:p", change the end tag "/text:h" following this insertion 273 to a "/text:p" element. 274 - Otherwise, copy the text content of the "text:deletion" element in 275 place of the change mark. 276 """ 277 278 _tag = "text:deletion" 279 280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner 316 317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element) 332 333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:
- If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
- If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
- Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner
Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph
Arguments:
as_text -- boolean
no_header -- boolean
Return: Paragraph and Header list
317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element)
Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.
Arguments:
paragraph_or_list -- Paragraph or Header element (or list)
333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
345class TextFormatChange(TextInsertion): 346 """The TextFormatChange "text:format-change" element represents any change 347 in formatting attributes. The region where the change took place is 348 marked by "text:change-start", "text:change-end" or "text:change" 349 elements. 350 351 Note: This element does not contain formatting changes that have taken 352 place. 353 """ 354 355 _tag = "text:format-change"
The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.
Note: This element does not contain formatting changes that have taken place.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
134class TextInsertion(Element): 135 """The TextInsertion "text:insertion" element contains the information 136 that identifies the person responsible for a change and the date of 137 that change. This information may also contain one or more "text:p" 138 Paragraph which contain a comment on the insertion. The 139 TextInsertion element's parent "text:changed-region" element has an 140 xml:id or text:id attribute, the value of which binds that parent 141 element to the text:change-id attribute on the "text:change-start" 142 and "text:change-end" elements. 143 """ 144 145 _tag = "text:insertion" 146 147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None 156 157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 ) 194 195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info") 201 202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.
147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None
Return: None.
157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 )
Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.
If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info")
Get the ChangeInfo child of the element.
Return: ChangeInfo element.
202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
467class TocEntryTemplate(Element): 468 """ODF "text:table-of-content-entry-template" 469 470 Arguments: 471 472 style -- str 473 """ 474 475 _tag = "text:table-of-content-entry-template" 476 _properties = (PropDef("style", "text:style-name"),) 477 478 def __init__( 479 self, 480 style: str | None = None, 481 outline_level: int | None = None, 482 **kwargs: Any, 483 ) -> None: 484 super().__init__(**kwargs) 485 if self._do_init: 486 if style: 487 self.style = style 488 if outline_level: 489 self.outline_level = outline_level 490 491 @property 492 def outline_level(self) -> int | None: 493 return self.get_attribute_integer("text:outline-level") 494 495 @outline_level.setter 496 def outline_level(self, level: int) -> None: 497 self.set_attribute("text:outline-level", str(level)) 498 499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
ODF "text:table-of-content-entry-template"
Arguments:
style -- str
499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
452class TrackedChanges(Element): 453 """The TrackedChanges "text:tracked-changes" element acts as a container 454 for TextChangedRegion elements that represent changes in a certain 455 scope of an OpenDocument document. This scope is the element in which 456 the TrackedChanges element occurs. Changes in this scope shall be 457 tracked by TextChangedRegion elements contained in the 458 TrackedChanges element in this scope. If a TrackedChanges 459 element is absent, there are no tracked changes in the corresponding 460 scope. In this case, all change mark elements in this scope shall be 461 ignored. 462 """ 463 464 _tag = "text:tracked-changes" 465 466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result 489 490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.
466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result
490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
212class UserDefined(ElementTyped): 213 """Return a user defined field "text:user-defined". If the current 214 document is provided, try to extract the content of the meta user defined 215 field of same name. 216 217 Arguments: 218 219 name -- str, name of the user defined field 220 221 value -- python typed value, value of the field 222 223 value_type -- str, office:value-type known type 224 225 text -- str 226 227 style -- str 228 229 from_document -- ODF document 230 """ 231 232 _tag = "text:user-defined" 233 _properties = ( 234 PropDef("name", "text:name"), 235 PropDef("style", "style:data-style-name"), 236 ) 237 238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.
Arguments:
name -- str, name of the user defined field
value -- python typed value, value of the field
value_type -- str, office:value-type known type
text -- str
style -- str
from_document -- ODF document
238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
146class UserFieldDecl(ElementTyped): 147 _tag = "text:user-field-decl" 148 _properties = (PropDef("name", "text:name"),) 149 150 def __init__( 151 self, 152 name: str | None = None, 153 value: Any = None, 154 value_type: str | None = None, 155 **kwargs: Any, 156 ) -> None: 157 super().__init__(**kwargs) 158 if self._do_init: 159 if name: 160 self.name = name 161 self.set_value_and_type(value=value, value_type=value_type) 162 163 def set_value(self, value: Any) -> None: 164 name = self.get_attribute("text:name") 165 self.clear() 166 self.set_value_and_type(value=value) 167 self.set_attribute("text:name", name)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
173class UserFieldGet(ElementTyped): 174 _tag = "text:user-field-get" 175 _properties = ( 176 PropDef("name", "text:name"), 177 PropDef("style", "style:data-style-name"), 178 ) 179 180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
376class VarChapter(Element): 377 _tag = "text:chapter" 378 _properties = ( 379 PropDef("display", "text:display"), 380 PropDef("outline_level", "text:outline-level"), 381 ) 382 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 383 "number", 384 "name", 385 "number-and-name", 386 "plain-number", 387 "plain-number-and-name", 388 } 389 390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
456class VarCreationDate(Element): 457 _tag = "text:creation-date" 458 _properties = ( 459 PropDef("fixed", "text:fixed"), 460 PropDef("data_style", "style:data-style-name"), 461 ) 462 463 def __init__( 464 self, 465 fixed: bool = False, 466 data_style: str | None = None, 467 **kwargs: Any, 468 ) -> None: 469 super().__init__(**kwargs) 470 if self._do_init: 471 if fixed: 472 self.fixed = True 473 if data_style: 474 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
480class VarCreationTime(Element): 481 _tag = "text:creation-time" 482 _properties = ( 483 PropDef("fixed", "text:fixed"), 484 PropDef("data_style", "style:data-style-name"), 485 ) 486 487 def __init__( 488 self, 489 fixed: bool = False, 490 data_style: str | None = None, 491 **kwargs: Any, 492 ) -> None: 493 super().__init__(**kwargs) 494 if self._do_init: 495 if fixed: 496 self.fixed = True 497 if data_style: 498 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
305class VarDate(Element): 306 _tag = "text:date" 307 _properties = ( 308 PropDef("date", "text:date-value"), 309 PropDef("fixed", "text:fixed"), 310 PropDef("data_style", "style:data-style-name"), 311 PropDef("date_adjust", "text:date-adjust"), 312 ) 313 314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class VarDecl(Element): 41 _tag = "text:variable-decl" 42 _properties = ( 43 PropDef("name", "text:name"), 44 PropDef("value_type", "office:value-type"), 45 ) 46 47 def __init__( 48 self, 49 name: str | None = None, 50 value_type: str | None = None, 51 **kwargs: Any, 52 ) -> None: 53 super().__init__(**kwargs) 54 if self._do_init: 55 if name: 56 self.name = name 57 if value_type: 58 self.value_type = value_type
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
411class VarFileName(Element): 412 _tag = "text:file-name" 413 _properties = ( 414 PropDef("display", "text:display"), 415 PropDef("fixed", "text:fixed"), 416 ) 417 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 418 "full", 419 "path", 420 "name", 421 "name-and-extension", 422 } 423 424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
display can be: 'full', 'path', 'name' or 'name-and-extension'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
111class VarGet(ElementTyped): 112 _tag = "text:variable-get" 113 _properties = ( 114 PropDef("name", "text:name"), 115 PropDef("style", "style:data-style-name"), 116 ) 117 118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
443class VarInitialCreator(Element): 444 _tag = "text:initial-creator" 445 _properties = (PropDef("fixed", "text:fixed"),) 446 447 def __init__(self, fixed: bool = False, **kwargs: Any) -> None: 448 super().__init__(**kwargs) 449 if self._do_init and fixed: 450 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
270class VarPageNumber(Element): 271 """ 272 select_page -- string in ('previous', 'current', 'next') 273 274 page_adjust -- int (to add or subtract to the page number) 275 """ 276 277 _tag = "text:page-number" 278 _properties = ( 279 PropDef("select_page", "text:select-page"), 280 PropDef("page_adjust", "text:page-adjust"), 281 ) 282 283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
select_page -- string in ('previous', 'current', 'next')
page_adjust -- int (to add or subtract to the page number)
283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
64class VarSet(ElementTyped): 65 _tag = "text:variable-set" 66 _properties = ( 67 PropDef("name", "text:name"), 68 PropDef("style", "style:data-style-name"), 69 PropDef("display", "text:display"), 70 ) 71 72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore 95 96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore
96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
341class VarTime(Element): 342 _tag = "text:time" 343 _properties = ( 344 PropDef("time", "text:time-value"), 345 PropDef("fixed", "text:fixed"), 346 PropDef("data_style", "style:data-style-name"), 347 PropDef("time_adjust", "text:time-adjust"), 348 ) 349 350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class XmlPart: 38 """Representation of an XML part. 39 40 Abstraction of the XML library behind. 41 """ 42 43 def __init__(self, part_name: str, container: Container) -> None: 44 self.part_name = part_name 45 self.container = container 46 47 # Internal state 48 self.__tree: _ElementTree | None = None 49 self.__root: Element | None = None 50 51 def _get_tree(self) -> _ElementTree: 52 if self.__tree is None: 53 part = self.container.get_part(self.part_name) 54 self.__tree = parse(BytesIO(part)) # type: ignore 55 return self.__tree 56 57 def __repr__(self) -> str: 58 return f"<{self.__class__.__name__} part_name={self.part_name}>" 59 60 # Public API 61 62 @property 63 def root(self) -> Element: 64 if self.__root is None: 65 tree = self._get_tree() 66 self.__root = Element.from_tag(tree.getroot()) 67 return self.__root 68 69 def get_elements(self, xpath_query: str) -> list[Element | Text]: 70 root = self.root 71 return root.xpath(xpath_query) 72 73 def get_element(self, xpath_query: str) -> Any: 74 result = self.get_elements(xpath_query) 75 if not result: 76 return None 77 return result[0] 78 79 def delete_element(self, child: Element) -> None: 80 child.delete() 81 82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query) 88 89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone 102 103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
Representation of an XML part.
Abstraction of the XML library behind.
82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query)
Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.
89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone
103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
187def create_table_cell_style( 188 border: str | None = None, 189 border_top: str | None = None, 190 border_bottom: str | None = None, 191 border_left: str | None = None, 192 border_right: str | None = None, 193 padding: str | None = None, 194 padding_top: str | None = None, 195 padding_bottom: str | None = None, 196 padding_left: str | None = None, 197 padding_right: str | None = None, 198 background_color: str | tuple | None = None, 199 shadow: str | None = None, 200 color: str | tuple | None = None, 201) -> Style: 202 """Return a cell style. 203 204 The borders arguments must be some style attribute strings or None, see the 205 method 'make_table_cell_border_string' to generate them. 206 If the 'border' argument as the value 'default', the default style 207 "0.06pt solid #000000" is used for the 4 borders. 208 If any value is used for border, it is used for the 4 borders, else any of 209 the 4 borders can be specified by it's own string. If all the border, 210 border_top, border_bottom, ... arguments are None, an empty border is used 211 (ODF value is fo:border="none"). 212 213 Padding arguments are string specifying a length (e.g. "0.5mm")". If 214 'padding' is provided, it is used for the 4 sides, else any of 215 the 4 sides padding can be specified by it's own string. Default padding is 216 no padding. 217 218 Arguments: 219 220 border -- str, style string for borders on four sides 221 222 border_top -- str, style string for top if no 'border' argument 223 224 border_bottom -- str, style string for bottom if no 'border' argument 225 226 border_left -- str, style string for left if no 'border' argument 227 228 border_right -- str, style string for right if no 'border' argument 229 230 padding -- str, style string for padding on four sides 231 232 padding_top -- str, style string for top if no 'padding' argument 233 234 padding_bottom -- str, style string for bottom if no 'padding' argument 235 236 padding_left -- str, style string for left if no 'padding' argument 237 238 padding_right -- str, style string for right if no 'padding' argument 239 240 background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 241 242 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 243 244 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 245 246 Return : Style 247 """ 248 if border == "default": 249 border = make_table_cell_border_string() # default border 250 if border is not None: 251 # use the border value for 4 sides. 252 border_bottom = border_top = border_left = border_right = None 253 if ( 254 border is None 255 and border_bottom is None 256 and border_top is None 257 and border_left is None 258 and border_right is None 259 ): 260 border = "none" 261 if padding is not None: 262 # use the padding value for 4 sides. 263 padding_bottom = padding_top = padding_left = padding_right = None 264 cell_style = Style( 265 "table-cell", 266 area="table-cell", 267 border=border, 268 border_top=border_top, 269 border_bottom=border_bottom, 270 border_left=border_left, 271 border_right=border_right, 272 padding=padding, 273 padding_top=padding_top, 274 padding_bottom=padding_bottom, 275 padding_left=padding_left, 276 padding_right=padding_right, 277 background_color=background_color, 278 shadow=shadow, 279 ) 280 if color: 281 cell_style.set_properties(area="text", color=color) 282 return cell_style
Return a cell style.
The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").
Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.
Arguments:
border -- str, style string for borders on four sides
border_top -- str, style string for top if no 'border' argument
border_bottom -- str, style string for bottom if no 'border' argument
border_left -- str, style string for left if no 'border' argument
border_right -- str, style string for right if no 'border' argument
padding -- str, style string for padding on four sides
padding_top -- str, style string for top if no 'padding' argument
padding_bottom -- str, style string for bottom if no 'padding' argument
padding_left -- str, style string for left if no 'padding' argument
padding_right -- str, style string for right if no 'padding' argument
background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Return : Style
1108def default_currency_style() -> Element: 1109 return Element.from_tag( 1110 """<number:currency-style style:name="lpod-default-currency-style"> 1111 <number:text>-</number:text> 1112 <number:number number:decimal-places="2" 1113 number:min-integer-digits="1" 1114 number:grouping="true"/> 1115 <number:text> </number:text> 1116 <number:currency-symbol 1117 number:language="fr" 1118 number:country="FR">€</number:currency-symbol> 1119 </number:currency-style>""" 1120 )
1087def default_date_style() -> Element: 1088 return Element.from_tag( 1089 """ 1090 <number:date-style style:name="lpod-default-date-style"> 1091 <number:year number:style="long"/> 1092 <number:text>-</number:text> 1093 <number:month number:style="long"/> 1094 <number:text>-</number:text> 1095 <number:day number:style="long"/> 1096 </number:date-style>""" 1097 )
42def default_frame_position_style( 43 name: str = "FramePosition", 44 horizontal_pos: str = "from-left", 45 vertical_pos: str = "from-top", 46 horizontal_rel: str = "paragraph", 47 vertical_rel: str = "paragraph", 48) -> Style: 49 """Helper style for positioning frames in desktop applications that need 50 it. 51 52 Default arguments should be enough. 53 54 Use the returned Style as the frame style or build a new graphic style 55 with this style as the parent. 56 """ 57 return Style( 58 family="graphic", 59 name=name, 60 horizontal_pos=horizontal_pos, 61 horizontal_rel=horizontal_rel, 62 vertical_pos=vertical_pos, 63 vertical_rel=vertical_rel, 64 )
Helper style for positioning frames in desktop applications that need it.
Default arguments should be enough.
Use the returned Style as the frame style or build a new graphic style with this style as the parent.
1064def default_percentage_style() -> Element: 1065 return Element.from_tag( 1066 """<number:percentage-style 1067 style:name="lpod-default-percentage-style"> 1068 <number:number number:decimal-places="2" 1069 number:min-integer-digits="1"/> 1070 <number:text>%</number:text> 1071 </number:percentage-style>""" 1072 )
1075def default_time_style() -> Element: 1076 return Element.from_tag( 1077 """<number:time-style style:name="lpod-default-time-style"> 1078 <number:hours number:style="long"/> 1079 <number:text>:</number:text> 1080 <number:minutes number:style="long"/> 1081 <number:text>:</number:text> 1082 <number:seconds number:style="long"/> 1083 </number:time-style>""" 1084 )
150def default_toc_level_style(level: int) -> Style: 151 """Generate an automatic default style for the given TOC level.""" 152 tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".") 153 position = 17.5 - (0.5 * level) 154 tab_stop.style_position = f"{position}cm" 155 tab_stops = Element.from_tag("style:tab-stops") 156 tab_stops.append(tab_stop) 157 properties = Element.from_tag("style:paragraph-properties") 158 properties.append(tab_stops) 159 toc_style_level = Style( 160 family="paragraph", 161 name=_toc_entry_style_name(level), 162 parent=f"Contents_20_{level}", 163 ) 164 toc_style_level.append(properties) 165 return toc_style_level
Generate an automatic default style for the given TOC level.
26def hex2rgb(color: str) -> tuple[int, int, int]: 27 """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) 28 tuple. 29 30 Arguments: 31 32 color -- str 33 34 Return: tuple 35 """ 36 code = color[1:] 37 if not (len(color) == 7 and color[0] == "#" and code.isalnum()): 38 raise ValueError(f'"{color}" is not a valid color') 39 red = int(code[:2], 16) 40 green = int(code[2:4], 16) 41 blue = int(code[4:6], 16) 42 return (red, green, blue)
Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple.
Arguments:
color -- str
Return: tuple
81def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None: 82 """Convert a color definition of type tuple or string to hexadecimal 83 representation. 84 85 Empty string is converted to black. 86 None is converted to None. 87 88 Arguments: 89 90 color -- str or tuple or None 91 92 Return: str or None 93 """ 94 if color is None: 95 return None 96 if isinstance(color, tuple): 97 return rgb2hex(color) 98 if not isinstance(color, str): 99 raise TypeError(f'Invalid color argument "{color!r}"') 100 color = color.strip() 101 if not color: 102 return "#000000" 103 if color.startswith("#"): 104 return color 105 return rgb2hex(color)
Convert a color definition of type tuple or string to hexadecimal representation.
Empty string is converted to black. None is converted to None.
Arguments:
color -- str or tuple or None
Return: str or None
167def make_table_cell_border_string( 168 thick: str | float | int | None = None, 169 line: str | None = None, 170 color: str | tuple | None = None, 171) -> str: 172 """Returns a string for style:table-cell-properties fo:border, 173 with default : "0.06pt solid #000000" 174 175 thick -- str or float or int 176 line -- str 177 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 178 179 Returns : str 180 """ 181 thick_string = _make_thick_string(thick) 182 line_string = _make_line_string(line) 183 color_string = hexa_color(color) or "#000000" 184 return " ".join((thick_string, line_string, color_string))
Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"
thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Returns : str
45def rgb2hex(color: str | tuple[int, int, int]) -> str: 46 """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" 47 hexadecimal representation. 48 49 Arguments: 50 51 color -- str or tuple 52 53 Return: str 54 55 Examples:: 56 57 >>> rgb2hex('yellow') 58 '#FFFF00' 59 >>> rgb2hex((238, 130, 238)) 60 '#EE82EE' 61 """ 62 if isinstance(color, str): 63 try: 64 code = CSS3_COLORMAP[color.lower()] 65 except KeyError as e: 66 raise KeyError(f'Color "{color}" is unknown in CSS color list') from e 67 elif isinstance(color, tuple): 68 if len(color) != 3: 69 raise ValueError("Color must be a 3-tuple") 70 code = color 71 else: 72 raise TypeError(f'Invalid color "{color}"') 73 for channel in code: 74 if not 0 <= channel <= 255: 75 raise ValueError( 76 f'Invalid color "{color}", channel must be between 0 and 255' 77 ) 78 return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"
Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation.
Arguments:
color -- str or tuple
Return: str
Examples::
>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'